From 58a3fead0f19397a7e52c7ee7afaef150876d981 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 10 Jun 2026 12:58:59 +0200 Subject: [PATCH] Revert "Merge pull request #197 from microsoft/sandy081/multi-chat-impl" This reverts commit eeb87cd24eaf9e9f0c4761b8940db12826af2986, reversing changes made to 47903537c9441731128e62499754593cea5764d8. --- CHANGELOG.md | 15 - clients/go/CHANGELOG.md | 17 +- clients/go/ahp/multi_host_state_mirror.go | 28 +- clients/go/ahp/reducers.go | 444 +- clients/go/ahp/reducers_fixture_test.go | 11 +- clients/go/ahptypes/actions.generated.go | 594 +- clients/go/ahptypes/commands.generated.go | 32 +- clients/go/ahptypes/common.go | 22 - .../go/ahptypes/notifications.generated.go | 5 +- clients/go/ahptypes/state.generated.go | 557 +- clients/go/examples/reducers_demo/main.go | 36 +- clients/kotlin/CHANGELOG.md | 16 +- .../microsoft/agenthostprotocol/Reducers.kt | 684 ++- .../generated/Actions.generated.kt | 435 +- .../generated/Commands.generated.kt | 48 - .../generated/Notifications.generated.kt | 5 +- .../generated/State.generated.kt | 443 +- .../DiscriminatedUnionTest.kt | 32 +- .../FixtureDrivenReducerTest.kt | 18 +- .../agenthostprotocol/ReducersTest.kt | 116 +- clients/rust/CHANGELOG.md | 17 +- clients/rust/crates/ahp-types/src/actions.rs | 414 +- clients/rust/crates/ahp-types/src/commands.rs | 44 +- .../crates/ahp-types/src/notifications.rs | 5 +- clients/rust/crates/ahp-types/src/state.rs | 314 +- .../crates/ahp/src/multi_host_state_mirror.rs | 23 +- clients/rust/crates/ahp/src/reducers.rs | 513 +- .../ahp/tests/multi_host_state_mirror.rs | 7 +- .../Generated/Actions.generated.swift | 496 +- .../Generated/Commands.generated.swift | 57 - .../Generated/Notifications.generated.swift | 5 +- .../Generated/State.generated.swift | 431 +- .../AgentHostProtocol/NativeReducer.swift | 696 ++- .../Sources/AgentHostProtocol/Reducers.swift | 504 +- .../AHPStateMirror.swift | 19 +- .../MultiHostStateMirror.swift | 18 +- .../AHPClientTests.swift | 2 +- .../AHPStateMirrorTests.swift | 4 +- .../MultiHostStateMirrorTests.swift | 6 +- .../FixtureDrivenReducerTests.swift | 4 - .../NativeReducerTests.swift | 38 +- .../ReducersTests.swift | 65 +- clients/swift/CHANGELOG.md | 16 +- clients/typescript/CHANGELOG.md | 16 +- docs/.vitepress/config.mts | 2 - docs/specification/chat-channel.md | 137 - docs/specification/session-channel.md | 84 +- schema/actions.schema.json | 5367 ++++++++--------- schema/commands.schema.json | 4723 +++++++-------- schema/errors.schema.json | 3529 +++++------ schema/notifications.schema.json | 3482 ++++++----- schema/state.schema.json | 3724 ++++++------ scripts/find-protocol-sources.ts | 1 - scripts/generate-action-origin.ts | 35 +- scripts/generate-go.ts | 272 +- scripts/generate-kotlin.ts | 231 +- scripts/generate-markdown.ts | 31 - scripts/generate-rust.ts | 229 +- scripts/generate-swift.ts | 231 +- types/action-origin.generated.ts | 206 +- types/actions.ts | 1 - types/channels-chat/actions.ts | 544 -- types/channels-chat/commands.ts | 60 - types/channels-chat/reducer.ts | 663 -- types/channels-chat/state.ts | 1149 ---- types/channels-session/actions.ts | 506 +- types/channels-session/commands.ts | 18 +- types/channels-session/reducer.ts | 647 +- types/channels-session/state.ts | 1087 +++- types/commands.ts | 1 - types/common/actions.ts | 147 +- types/common/messages.ts | 6 - types/common/state.ts | 4 +- types/index.ts | 106 +- types/messages.test.ts | 1 - types/reducers.test.ts | 22 +- types/reducers.ts | 1 - types/state.ts | 1 - .../reducers/003-session-ready.json | 4 +- .../reducers/004-session-creationfailed.json | 6 +- .../reducers/005-session-turnstarted.json | 34 +- ...messageid-removes-from-queuedmessages.json | 54 +- ...messageid-removes-last-queued-message.json | 36 +- ...eid-removes-matching-steering-message.json | 36 +- ...ageid-does-not-touch-pending-messages.json | 54 +- .../010-session-delta-appends-content.json | 38 +- ...sion-delta-with-wrong-turnid-is-no-op.json | 34 +- ...ion-delta-without-activeturn-is-no-op.json | 34 +- ...on-responsepart-adds-to-responseparts.json | 34 +- ...4-session-turncomplete-finalizes-turn.json | 38 +- ...-session-turncancelled-finalizes-turn.json | 34 +- ...ssion-error-finalizes-turn-with-error.json | 34 +- ...-force-cancels-in-progress-tool-calls.json | 36 +- ...rncomplete-with-wrong-turnid-is-no-op.json | 34 +- ...-start-delta-ready-confirmed-complete.json | 42 +- ...h-auto-confirm-transitions-to-running.json | 36 +- ...-call-denied-transitions-to-cancelled.json | 38 +- ...-result-confirmation-pending-approved.json | 40 +- ...d-cancelled-with-result-denied-reason.json | 40 +- ...nding-confirmation-defaults-confirmed.json | 38 +- ...ions-for-unknown-toolcallid-are-no-op.json | 34 +- ...ing-tool-back-to-pending-confirmation.json | 38 +- ...-approved-transitions-back-to-running.json | 40 +- ...ation-denied-transitions-to-cancelled.json | 40 +- ...-non-streaming-non-running-tool-calls.json | 34 +- ...30-session-titlechanged-updates-title.json | 4 +- ...on-usage-updates-usage-on-active-turn.json | 34 +- ...n-reasoning-appends-reasoning-content.json | 38 +- ...33-session-modelchanged-updates-model.json | 4 +- ...-servertoolschanged-sets-server-tools.json | 6 +- ...ssion-activeclientchanged-sets-client.json | 6 +- ...ion-activeclientchanged-unsets-client.json | 8 +- ...ctiveclienttoolschanged-updates-tools.json | 8 +- ...changed-without-activeclient-is-no-op.json | 4 +- .../reducers/039-set-steering-message.json | 34 +- ...040-replace-existing-steering-message.json | 34 +- ...-message-content-via-set-with-same-id.json | 34 +- .../reducers/042-remove-steering-message.json | 34 +- ...ng-message-is-no-op-for-mismatched-id.json | 34 +- ...ing-message-is-no-op-when-none-exists.json | 34 +- .../045-set-a-new-queued-message.json | 34 +- ...-append-queued-message-when-id-is-new.json | 34 +- ...ssage-in-place-when-id-already-exists.json | 34 +- .../reducers/048-remove-a-queued-message.json | 34 +- ...ueued-message-sets-array-to-undefined.json | 34 +- ...ueued-message-is-no-op-for-unknown-id.json | 34 +- ...-message-is-no-op-when-array-is-empty.json | 34 +- ...g-and-queued-messages-are-independent.json | 36 +- .../053-reorders-queued-messages.json | 34 +- ...not-in-order-at-end-in-original-order.json | 34 +- .../055-ignores-unknown-ids-in-reorder.json | 34 +- ...serves-all-messages-in-original-order.json | 34 +- ...s-no-op-when-no-queued-messages-exist.json | 34 +- ...mizationschanged-replaces-entire-list.json | 6 +- ...nged-replaces-existing-customizations.json | 8 +- ...on-customizationtoggled-toggles-by-id.json | 8 +- ...zationtoggled-is-no-op-for-unknown-id.json | 8 +- ...s-no-op-when-customizations-undefined.json | 4 +- ...on-truncated-keeps-turns-up-to-turnid.json | 34 +- ...uncated-keeps-all-when-turnid-is-last.json | 34 +- ...keeps-only-first-when-turnid-is-first.json | 34 +- ...ncated-clears-all-when-turnid-omitted.json | 34 +- ...7-session-truncated-drops-active-turn.json | 34 +- ...ps-active-turn-even-when-clearing-all.json | 34 +- ...truncated-is-no-op-for-unknown-turnid.json | 34 +- ...w-with-tool-calls-and-re-confirmation.json | 66 +- ...n-isreadchanged-marks-session-as-read.json | 4 +- ...isreadchanged-marks-session-as-unread.json | 4 +- ...ivedchanged-marks-session-as-archived.json | 4 +- ...-isarchivedchanged-unarchives-session.json | 4 +- .../075-turnstarted-clears-isread.json | 34 +- ...olcall-contentchanged-updates-running.json | 34 +- ...lcall-contentchanged-noop-non-running.json | 34 +- ...call-contentchanged-replaces-existing.json | 34 +- ...-session-unknown-action-type-is-no-op.json | 4 +- ...0-toolcalldelta-wrong-turnid-is-no-op.json | 34 +- ...91-responsepart-wrong-turnid-is-no-op.json | 34 +- ...2-toolcallstart-wrong-turnid-is-no-op.json | 34 +- .../093-usage-wrong-turnid-is-no-op.json | 34 +- ...094-delta-nonexistent-partid-is-no-op.json | 34 +- ...5-toolcalldelta-wrong-status-is-no-op.json | 34 +- ...olcallconfirmed-wrong-status-is-no-op.json | 34 +- ...oolcallcomplete-wrong-status-is-no-op.json | 34 +- ...resultconfirmed-wrong-status-is-no-op.json | 34 +- ...dturn-force-cancels-running-tool-call.json | 34 +- ...ta-targeting-toolcall-partid-is-no-op.json | 34 +- ...olcalldelta-without-invocationmessage.json | 34 +- ...ning-targeting-non-reasoning-is-no-op.json | 34 +- .../103-delta-skips-parts-without-id.json | 34 +- ...on-input-full-draft-and-complete-flow.json | 42 +- ...on-input-requested-with-drafts-status.json | 36 +- ...nput-turn-end-cleans-turn-scoped-only.json | 34 +- ...session-input-upsert-and-clear-answer.json | 38 +- ...ssion-input-unknown-actions-are-no-op.json | 36 +- ...t-completion-and-truncation-filtering.json | 36 +- ...confirmation-sets-input-needed-status.json | 36 +- ...confirmation-sets-input-needed-status.json | 38 +- ...nfigchanged-merges-into-config-values.json | 4 +- ...igchanged-noops-when-config-undefined.json | 4 +- ...session-modelchanged-with-modelconfig.json | 4 +- ...olcallready-with-confirmation-options.json | 36 +- ...onfirmed-approved-with-selectedoption.json | 38 +- ...igchanged-replace-replaces-all-values.json | 4 +- ...lconfirmed-denied-with-selectedoption.json | 38 +- ...edoption-carries-through-to-completed.json | 40 +- ...n-carries-through-result-confirmation.json | 42 +- ...doption-carries-through-result-denied.json | 42 +- ...session-activitychanged-sets-activity.json | 4 +- ...ssion-activitychanged-clears-activity.json | 4 +- .../135-session-metachanged-sets-meta.json | 8 +- ...onupdated-replaces-existing-container.json | 30 +- ...stomizationupdated-appends-unknown-id.json | 16 +- ...ion-customizationupdated-creates-list.json | 6 +- ...sion-changesetschanged-sets-catalogue.json | 6 +- ...on-changesetschanged-clears-catalogue.json | 6 +- .../147-session-agentchanged-sets-agent.json | 4 +- ...ion-ready-preserves-inprogress-status.json | 26 +- ...148-session-agentchanged-clears-agent.json | 4 +- ...9-session-agentchanged-replaces-agent.json | 4 +- ...emoved-removes-container-and-children.json | 8 +- ...on-customizationremoved-removes-child.json | 8 +- ...-customizationremoved-noop-unknown-id.json | 8 +- .../156-session-default-chat-changed.json | 36 - ...th-editedtoolinput-overrides-original.json | 40 +- ...statechanged-upserts-top-level-server.json | 16 +- ...0-session-default-chat-changed-unsets.json | 35 - ...rstatechanged-upserts-container-child.json | 16 +- .../161-chat-turn-lifecycle-on-chat.json | 76 - ...mcpserverstatechanged-noop-unknown-id.json | 16 +- ...mcpserverstatechanged-noop-non-mcp-id.json | 12 +- ...ies-mcp-contributor-through-lifecycle.json | 40 +- .../170-session-chatadded-appends.json | 48 - .../171-session-chatadded-upserts.json | 56 - .../reducers/172-session-chatremoved.json | 60 - .../reducers/173-session-chatupdated.json | 56 - .../174-session-chatremoved-noop.json | 52 - .../175-session-chatupdated-noop.json | 51 - .../220-toolcall-actions-update-meta.json | 80 +- types/version/message-checks.ts | 2 - types/version/registry.ts | 48 +- 220 files changed, 17918 insertions(+), 20722 deletions(-) delete mode 100644 docs/specification/chat-channel.md delete mode 100644 types/channels-chat/actions.ts delete mode 100644 types/channels-chat/commands.ts delete mode 100644 types/channels-chat/reducer.ts delete mode 100644 types/channels-chat/state.ts delete mode 100644 types/test-cases/reducers/156-session-default-chat-changed.json delete mode 100644 types/test-cases/reducers/160-session-default-chat-changed-unsets.json delete mode 100644 types/test-cases/reducers/161-chat-turn-lifecycle-on-chat.json delete mode 100644 types/test-cases/reducers/170-session-chatadded-appends.json delete mode 100644 types/test-cases/reducers/171-session-chatadded-upserts.json delete mode 100644 types/test-cases/reducers/172-session-chatremoved.json delete mode 100644 types/test-cases/reducers/173-session-chatupdated.json delete mode 100644 types/test-cases/reducers/174-session-chatremoved-noop.json delete mode 100644 types/test-cases/reducers/175-session-chatupdated-noop.json diff --git a/CHANGELOG.md b/CHANGELOG.md index c437b0f6..f9b92744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,26 +47,11 @@ Spec version: `0.4.0` Resolving or re-anchoring an annotation no longer requires replacing the whole annotation via `annotations/set`. Omitted fields are left unchanged; the annotation's `entries`, `id`, and `_meta` are never touched. -- `ahp-chat:` channel for per-chat conversation state; `SessionState.chats[]` catalog; `SessionState.defaultChat?` input-routing hint; `ChatOrigin` provenance union; `createChat` command. -- `ChatSummary.workingDirectory?` — optional per-chat working directory. When absent, chats inherit the session's `workingDirectory`. Enables agent-swarm patterns where multiple chats in one session operate on independent worktrees. -- Three discrete chat-catalog actions on the session channel — `session/chatAdded` (upsert by `summary.resource`), `session/chatRemoved`, and `session/chatUpdated` (partial-update with `Partial`) — mirroring the root-channel `root/sessionAdded` / `root/sessionRemoved` / `root/sessionSummaryChanged` pattern. - `RootState` now carries an optional `_meta` property bag for implementation-defined metadata about the agent host itself, mirroring the MCP `_meta` convention. A well-known `hostBuild` key may carry build information (version, commit, date) about the program hosting the agent host. -### Changed - -- `fetchTurns` and `completions` now target an `ahp-chat:` channel; `PROTOCOL_VERSION` bumped to `0.4.0`. -- `ChatState` is now **flat** — the previous `summary: ChatSummary` sub-object has been replaced by inlined `resource` / `title` / `status` / `activity` / `modifiedAt` / `model` / `agent` / `origin` / `workingDirectory` fields. `ChatSummary` remains as the standalone catalog entry on `SessionState.chats`. -- `ChatSummary.modifiedAt` and `ChatState.modifiedAt` are now ISO 8601 strings instead of numeric milliseconds. -- `SessionSummary` now documents how its aggregate fields (`status`, `activity`, `modifiedAt`) are derived from the session's chats, including `InputNeeded` / `Error` promotion when any chat raises the flag. - -### Removed - -- `SessionState.turns`, `SessionState.activeTurn`, `SessionState.steeringMessage`, `SessionState.queuedMessages`, `SessionState.inputRequests` (moved to `ChatState`). -- `session/chatsChanged` full-replacement action (replaced by `session/chatAdded` / `session/chatRemoved` / `session/chatUpdated`). - ## [0.3.0] — 2026-06-05 Spec version: `0.3.0` diff --git a/clients/go/CHANGELOG.md b/clients/go/CHANGELOG.md index dd4c5fff..d40b650a 100644 --- a/clients/go/CHANGELOG.md +++ b/clients/go/CHANGELOG.md @@ -33,23 +33,13 @@ tag whose matching `## [X.Y.Z]` heading is missing from this file. resending its entries. Handled by the annotations reducer (no-op on unknown id). -- `ahp-chat:` channel for per-chat conversation state; `SessionState.chats[]` catalog; `SessionState.defaultChat?` input-routing hint; `ChatOrigin` provenance union; `createChat` / `disposeChat` commands. -- `ChatSummary.WorkingDirectory` — optional per-chat working directory. Falls back to the session's `WorkingDirectory` when absent. -- Three discrete chat-catalog actions on the session channel — `SessionChatAddedAction` (upsert by `Summary.Resource`), `SessionChatRemovedAction`, and `SessionChatUpdatedAction` (partial-update payload). +### Added + - `RootState` now exposes an optional `_meta` property bag (`Meta map[string]json.RawMessage`) for implementation-defined agent-host metadata, such as a well-known `hostBuild` key carrying the host's build version/commit/date. -### Changed - -- `ChatState` is now flat — the previous embedded `Summary` has been replaced with inlined `Resource` / `Title` / `Status` / `Activity` / `ModifiedAt` / `Model` / `Agent` / `Origin` / `WorkingDirectory` fields. `ChatSummary` remains as the standalone catalog entry on `SessionState.Chats`. -- `ChatSummary.ModifiedAt` and `ChatState.ModifiedAt` are now ISO 8601 `string` values instead of integer milliseconds. - -### Removed - -- `SessionChatsChangedAction` (replaced by the three discrete chat-catalog actions above). - ## [0.3.0] — 2026-06-05 Implements AHP 0.3.0. @@ -95,14 +85,11 @@ Implements AHP 0.3.0. ### Changed -- Reducers split into per-chat and session-aggregate handlers to match the multi-chat protocol shape. -- `fetchTurns` and `completions` now target an `ahp-chat:` channel; `PROTOCOL_VERSION` bumped to `0.4.0`. - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. - Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed -- `SessionState.turns`, `SessionState.activeTurn`, `SessionState.steeringMessage`, `SessionState.queuedMessages`, `SessionState.inputRequests` (moved to `ChatState`). - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. ### Changed diff --git a/clients/go/ahp/multi_host_state_mirror.go b/clients/go/ahp/multi_host_state_mirror.go index 662d57a8..d0a90705 100644 --- a/clients/go/ahp/multi_host_state_mirror.go +++ b/clients/go/ahp/multi_host_state_mirror.go @@ -22,13 +22,12 @@ type HostedResourceKey struct { // The mirror has no opinion about how snapshots are kept in sync with // the server — that's the consumer's job, typically by feeding action // envelopes from a [HostSubscriptionEvent] stream through the matching -// [ApplyActionToRoot] / [ApplyActionToSession] / [ApplyActionToChat] / -// [ApplyActionToTerminal] reducer and re-storing the result. +// [ApplyActionToRoot] / [ApplyActionToSession] / [ApplyActionToTerminal] +// reducer and re-storing the result. type MultiHostStateMirror struct { mu sync.RWMutex roots map[string]ahptypes.RootState session map[HostedResourceKey]ahptypes.SessionState - chat map[HostedResourceKey]ahptypes.ChatState term map[HostedResourceKey]ahptypes.TerminalState changes map[HostedResourceKey]ahptypes.ChangesetState } @@ -38,7 +37,6 @@ func NewMultiHostStateMirror() *MultiHostStateMirror { return &MultiHostStateMirror{ roots: make(map[string]ahptypes.RootState), session: make(map[HostedResourceKey]ahptypes.SessionState), - chat: make(map[HostedResourceKey]ahptypes.ChatState), term: make(map[HostedResourceKey]ahptypes.TerminalState), changes: make(map[HostedResourceKey]ahptypes.ChangesetState), } @@ -76,22 +74,6 @@ func (m *MultiHostStateMirror) Session(hostID string, uri ahptypes.URI) (ahptype return v, ok } -// PutChat stores a chat snapshot under (hostID, uri). -func (m *MultiHostStateMirror) PutChat(hostID string, uri ahptypes.URI, c ahptypes.ChatState) { - m.mu.Lock() - defer m.mu.Unlock() - m.chat[HostedResourceKey{hostID, uri}] = c -} - -// Chat returns the chat snapshot at (hostID, uri), or -// (zero, false) if none is recorded. -func (m *MultiHostStateMirror) Chat(hostID string, uri ahptypes.URI) (ahptypes.ChatState, bool) { - m.mu.RLock() - defer m.mu.RUnlock() - v, ok := m.chat[HostedResourceKey{hostID, uri}] - return v, ok -} - // PutTerminal stores a terminal snapshot under (hostID, uri). func (m *MultiHostStateMirror) PutTerminal(hostID string, uri ahptypes.URI, t ahptypes.TerminalState) { m.mu.Lock() @@ -135,11 +117,6 @@ func (m *MultiHostStateMirror) DropHost(hostID string) { delete(m.session, k) } } - for k := range m.chat { - if k.HostID == hostID { - delete(m.chat, k) - } - } for k := range m.term { if k.HostID == hostID { delete(m.term, k) @@ -159,7 +136,6 @@ func (m *MultiHostStateMirror) DropResource(hostID string, uri ahptypes.URI) { defer m.mu.Unlock() k := HostedResourceKey{hostID, uri} delete(m.session, k) - delete(m.chat, k) delete(m.term, k) delete(m.changes, k) } diff --git a/clients/go/ahp/reducers.go b/clients/go/ahp/reducers.go index 8ff674ea..70e35df1 100644 --- a/clients/go/ahp/reducers.go +++ b/clients/go/ahp/reducers.go @@ -33,10 +33,9 @@ var ( nowProvider func() int64 = func() int64 { return time.Now().UnixMilli() } ) -// SetNowProvider overrides the clock reducers use to stamp modifiedAt fields. -// Chat modifiedAt values are formatted as ISO 8601 strings from this clock; -// session summary modifiedAt keeps the numeric millisecond timestamp. Pass nil -// to restore the default ([time.Now].UnixMilli). +// SetNowProvider overrides the function reducers call to stamp +// `summary.modifiedAt`. Useful for tests that need deterministic +// output. Pass nil to restore the default ([time.Now].UnixMilli). func SetNowProvider(fn func() int64) { nowMu.Lock() defer nowMu.Unlock() @@ -53,10 +52,6 @@ func nowMs() int64 { return nowProvider() } -func nowISOString() string { - return time.UnixMilli(nowMs()).UTC().Format("2006-01-02T15:04:05.000Z") -} - // ─── Status helpers ──────────────────────────────────────────────────── // statusActivityMask covers the mutually-exclusive activity bits @@ -125,7 +120,7 @@ func toolCallID(tc ahptypes.ToolCallState) string { return toolCallMeta(tc).id } -func hasPendingToolCallConfirmation(state *ahptypes.ChatState) bool { +func hasPendingToolCallConfirmation(state *ahptypes.SessionState) bool { if state.ActiveTurn == nil { return false } @@ -143,7 +138,7 @@ func hasPendingToolCallConfirmation(state *ahptypes.ChatState) bool { return false } -func summaryStatus(state *ahptypes.ChatState, terminal *ahptypes.SessionStatus) ahptypes.SessionStatus { +func summaryStatus(state *ahptypes.SessionState, terminal *ahptypes.SessionStatus) ahptypes.SessionStatus { var activity ahptypes.SessionStatus switch { case terminal != nil: @@ -155,24 +150,20 @@ func summaryStatus(state *ahptypes.ChatState, terminal *ahptypes.SessionStatus) default: activity = ahptypes.SessionStatusIdle } - return (state.Status &^ statusActivityMask) | activity + return (state.Summary.Status &^ statusActivityMask) | activity } -func refreshSummaryStatus(state *ahptypes.ChatState) { - state.Status = summaryStatus(state, nil) +func refreshSummaryStatus(state *ahptypes.SessionState) { + state.Summary.Status = summaryStatus(state, nil) } -func touchSessionModified(state *ahptypes.SessionState) { +func touchModified(state *ahptypes.SessionState) { state.Summary.ModifiedAt = nowMs() } -func touchChatModified(state *ahptypes.ChatState) { - state.ModifiedAt = nowISOString() -} - // ─── Active-turn helpers ─────────────────────────────────────────────── -func endTurn(state *ahptypes.ChatState, turnID string, turnState ahptypes.TurnState, terminalStatus *ahptypes.SessionStatus, errInfo *ahptypes.ErrorInfo) ReduceOutcome { +func endTurn(state *ahptypes.SessionState, turnID string, turnState ahptypes.TurnState, terminalStatus *ahptypes.SessionStatus, errInfo *ahptypes.ErrorInfo) ReduceOutcome { if state.ActiveTurn == nil || state.ActiveTurn.Id != turnID { return ReduceOutcomeNoOp } @@ -221,12 +212,12 @@ func endTurn(state *ahptypes.ChatState, turnID string, turnState ahptypes.TurnSt state.Turns = append(state.Turns, turn) state.InputRequests = nil - touchChatModified(state) - state.Status = summaryStatus(state, terminalStatus) + touchModified(state) + state.Summary.Status = summaryStatus(state, terminalStatus) return ReduceOutcomeApplied } -func upsertInputRequest(state *ahptypes.ChatState, req ahptypes.ChatInputRequest) { +func upsertInputRequest(state *ahptypes.SessionState, req ahptypes.SessionInputRequest) { existing := state.InputRequests found := -1 for i := range existing { @@ -244,9 +235,9 @@ func upsertInputRequest(state *ahptypes.ChatState, req ahptypes.ChatInputRequest existing = append(existing, req) } state.InputRequests = existing - state.Status = summaryStatus(state, nil) - touchChatModified(state) - state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsRead, false) + state.Summary.Status = summaryStatus(state, nil) + touchModified(state) + state.Summary.Status = withStatusFlag(state.Summary.Status, ahptypes.SessionStatusIsRead, false) } // ─── Customization helpers ───────────────────────────────────────────── @@ -315,7 +306,7 @@ func applyToggle(list []ahptypes.Customization, id string, enabled bool) bool { // ─── Active-turn mutation helpers ────────────────────────────────────── -func updateToolCall(state *ahptypes.ChatState, turnID, targetToolCallID string, updater func(ahptypes.ToolCallState) ahptypes.ToolCallState) ReduceOutcome { +func updateToolCall(state *ahptypes.SessionState, turnID, targetToolCallID string, updater func(ahptypes.ToolCallState) ahptypes.ToolCallState) ReduceOutcome { if state.ActiveTurn == nil || state.ActiveTurn.Id != turnID { return ReduceOutcomeNoOp } @@ -332,7 +323,7 @@ func updateToolCall(state *ahptypes.ChatState, turnID, targetToolCallID string, return ReduceOutcomeNoOp } -func updateResponsePart(state *ahptypes.ChatState, turnID, partID string, updater func(*ahptypes.ResponsePart)) ReduceOutcome { +func updateResponsePart(state *ahptypes.SessionState, turnID, partID string, updater func(*ahptypes.ResponsePart)) ReduceOutcome { if state.ActiveTurn == nil || state.ActiveTurn.Id != turnID { return ReduceOutcomeNoOp } @@ -390,36 +381,44 @@ func ApplyActionToRoot(state *ahptypes.RootState, action ahptypes.StateAction) R return ReduceOutcomeOutOfScope } -// ─── Chat Reducer ────────────────────────────────────────────────────── +// ─── Session Reducer ─────────────────────────────────────────────────── -// ApplyActionToChat applies action to the [ahptypes.ChatState] in -// place. Returns [ReduceOutcomeOutOfScope] for actions that target a -// different state tree. -func ApplyActionToChat(state *ahptypes.ChatState, action ahptypes.StateAction) ReduceOutcome { +// ApplyActionToSession applies action to the [ahptypes.SessionState] +// in place. Returns [ReduceOutcomeOutOfScope] for actions that target +// a different state tree. +func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAction) ReduceOutcome { switch a := action.Value.(type) { - case *ahptypes.ChatTurnStartedAction: + case *ahptypes.SessionReadyAction: + state.Lifecycle = ahptypes.SessionLifecycleReady + return ReduceOutcomeApplied + case *ahptypes.SessionCreationFailedAction: + state.Lifecycle = ahptypes.SessionLifecycleCreationFailed + errCopy := a.Error + state.CreationError = &errCopy + return ReduceOutcomeApplied + case *ahptypes.SessionTurnStartedAction: return applyTurnStarted(state, a) - case *ahptypes.ChatDeltaAction: + case *ahptypes.SessionDeltaAction: return updateResponsePart(state, a.TurnId, a.PartId, func(p *ahptypes.ResponsePart) { if m, ok := p.Value.(*ahptypes.MarkdownResponsePart); ok { m.Content += a.Content } }) - case *ahptypes.ChatResponsePartAction: + case *ahptypes.SessionResponsePartAction: if state.ActiveTurn == nil || state.ActiveTurn.Id != a.TurnId { return ReduceOutcomeNoOp } state.ActiveTurn.ResponseParts = append(state.ActiveTurn.ResponseParts, a.Part) return ReduceOutcomeApplied - case *ahptypes.ChatTurnCompleteAction: + case *ahptypes.SessionTurnCompleteAction: return endTurn(state, a.TurnId, ahptypes.TurnStateComplete, nil, nil) - case *ahptypes.ChatTurnCancelledAction: + case *ahptypes.SessionTurnCancelledAction: return endTurn(state, a.TurnId, ahptypes.TurnStateCancelled, nil, nil) - case *ahptypes.ChatErrorAction: + case *ahptypes.SessionErrorAction: errCopy := a.Error errStatus := ahptypes.SessionStatusError return endTurn(state, a.TurnId, ahptypes.TurnStateError, &errStatus, &errCopy) - case *ahptypes.ChatToolCallStartAction: + case *ahptypes.SessionToolCallStartAction: if state.ActiveTurn == nil || state.ActiveTurn.Id != a.TurnId { return ReduceOutcomeNoOp } @@ -435,33 +434,33 @@ func ApplyActionToChat(state *ahptypes.ChatState, action ahptypes.StateAction) R }}, }}) return ReduceOutcomeApplied - case *ahptypes.ChatToolCallDeltaAction: + case *ahptypes.SessionToolCallDeltaAction: return applyToolCallDelta(state, a) - case *ahptypes.ChatToolCallReadyAction: + case *ahptypes.SessionToolCallReadyAction: res := applyToolCallReady(state, a) if res == ReduceOutcomeApplied { refreshSummaryStatus(state) } return res - case *ahptypes.ChatToolCallConfirmedAction: + case *ahptypes.SessionToolCallConfirmedAction: res := applyToolCallConfirmed(state, a) if res == ReduceOutcomeApplied { refreshSummaryStatus(state) } return res - case *ahptypes.ChatToolCallCompleteAction: + case *ahptypes.SessionToolCallCompleteAction: res := applyToolCallComplete(state, a) if res == ReduceOutcomeApplied { refreshSummaryStatus(state) } return res - case *ahptypes.ChatToolCallResultConfirmedAction: + case *ahptypes.SessionToolCallResultConfirmedAction: res := applyToolCallResultConfirmed(state, a) if res == ReduceOutcomeApplied { refreshSummaryStatus(state) } return res - case *ahptypes.ChatToolCallContentChangedAction: + case *ahptypes.SessionToolCallContentChangedAction: return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState { if r, ok := tc.Value.(*ahptypes.ToolCallRunningState); ok { if a.Meta != nil { @@ -471,223 +470,31 @@ func ApplyActionToChat(state *ahptypes.ChatState, action ahptypes.StateAction) R } return tc }) - case *ahptypes.ChatUsageAction: + case *ahptypes.SessionTitleChangedAction: + state.Summary.Title = a.Title + touchModified(state) + return ReduceOutcomeApplied + case *ahptypes.SessionUsageAction: if state.ActiveTurn == nil || state.ActiveTurn.Id != a.TurnId { return ReduceOutcomeNoOp } usage := a.Usage state.ActiveTurn.Usage = &usage return ReduceOutcomeApplied - case *ahptypes.ChatReasoningAction: + case *ahptypes.SessionReasoningAction: return updateResponsePart(state, a.TurnId, a.PartId, func(p *ahptypes.ResponsePart) { if r, ok := p.Value.(*ahptypes.ReasoningResponsePart); ok { r.Content += a.Content } }) - case *ahptypes.ChatTruncatedAction: - return applyTruncated(state, a.TurnId) - case *ahptypes.ChatInputRequestedAction: - upsertInputRequest(state, a.Request) - return ReduceOutcomeApplied - case *ahptypes.ChatInputAnswerChangedAction: - return applyInputAnswerChanged(state, a) - case *ahptypes.ChatInputCompletedAction: - list := state.InputRequests - if list == nil { - return ReduceOutcomeNoOp - } - had := false - next := list[:0] - for _, r := range list { - if r.Id == a.RequestId { - had = true - continue - } - next = append(next, r) - } - if !had { - return ReduceOutcomeNoOp - } - if len(next) == 0 { - state.InputRequests = nil - } else { - state.InputRequests = next - } - refreshSummaryStatus(state) - touchChatModified(state) - return ReduceOutcomeApplied - case *ahptypes.ChatPendingMessageSetAction: - entry := ahptypes.PendingMessage{Id: a.Id, Message: a.Message} - switch a.Kind { - case ahptypes.PendingMessageKindSteering: - state.SteeringMessage = &entry - case ahptypes.PendingMessageKindQueued: - list := state.QueuedMessages - idx := -1 - for i := range list { - if list[i].Id == entry.Id { - idx = i - break - } - } - if idx >= 0 { - list[idx] = entry - } else { - list = append(list, entry) - } - state.QueuedMessages = list - } - return ReduceOutcomeApplied - case *ahptypes.ChatPendingMessageRemovedAction: - switch a.Kind { - case ahptypes.PendingMessageKindSteering: - if state.SteeringMessage != nil && state.SteeringMessage.Id == a.Id { - state.SteeringMessage = nil - return ReduceOutcomeApplied - } - return ReduceOutcomeNoOp - case ahptypes.PendingMessageKindQueued: - list := state.QueuedMessages - if list == nil { - return ReduceOutcomeNoOp - } - next := list[:0] - removed := false - for _, m := range list { - if m.Id == a.Id { - removed = true - continue - } - next = append(next, m) - } - if !removed { - return ReduceOutcomeNoOp - } - if len(next) == 0 { - state.QueuedMessages = nil - } else { - state.QueuedMessages = next - } - return ReduceOutcomeApplied - } - return ReduceOutcomeNoOp - case *ahptypes.ChatQueuedMessagesReorderedAction: - if state.QueuedMessages == nil { - return ReduceOutcomeNoOp - } - byID := make(map[string]ahptypes.PendingMessage, len(state.QueuedMessages)) - for _, m := range state.QueuedMessages { - byID[m.Id] = m - } - reordered := make([]ahptypes.PendingMessage, 0, len(byID)) - seen := make(map[string]struct{}, len(byID)) - for _, id := range a.Order { - if msg, ok := byID[id]; ok { - if _, dup := seen[id]; !dup { - seen[id] = struct{}{} - reordered = append(reordered, msg) - } - } - } - // Append messages absent from `order`, preserving their original - // relative order in state.QueuedMessages (mirrors the canonical - // TypeScript reducer in types/channels-session/reducer.ts). - for _, m := range state.QueuedMessages { - if _, in := seen[m.Id]; !in { - reordered = append(reordered, m) - } - } - state.QueuedMessages = reordered - return ReduceOutcomeApplied - } - return ReduceOutcomeOutOfScope -} - -func mergeChatSummaryPartial(summary *ahptypes.ChatSummary, changes ahptypes.PartialChatSummary) { - if changes.Title != nil { - summary.Title = *changes.Title - } - if changes.Status != nil { - summary.Status = *changes.Status - } - if changes.Activity != nil { - summary.Activity = changes.Activity - } - if changes.ModifiedAt != nil { - summary.ModifiedAt = *changes.ModifiedAt - } - if changes.Model != nil { - summary.Model = changes.Model - } - if changes.Agent != nil { - summary.Agent = changes.Agent - } - if changes.Origin != nil { - summary.Origin = changes.Origin - } - if changes.WorkingDirectory != nil { - summary.WorkingDirectory = changes.WorkingDirectory - } -} - -// ─── Session Reducer ─────────────────────────────────────────────────── - -// ApplyActionToSession applies action to the [ahptypes.SessionState] -// in place. Returns [ReduceOutcomeOutOfScope] for actions that target -// a different state tree. -func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAction) ReduceOutcome { - switch a := action.Value.(type) { - case *ahptypes.SessionReadyAction: - state.Lifecycle = ahptypes.SessionLifecycleReady - return ReduceOutcomeApplied - case *ahptypes.SessionCreationFailedAction: - state.Lifecycle = ahptypes.SessionLifecycleCreationFailed - errCopy := a.Error - state.CreationError = &errCopy - return ReduceOutcomeApplied - case *ahptypes.SessionChatAddedAction: - for i := range state.Chats { - if state.Chats[i].Resource == a.Summary.Resource { - state.Chats[i] = a.Summary - return ReduceOutcomeApplied - } - } - state.Chats = append(state.Chats, a.Summary) - return ReduceOutcomeApplied - case *ahptypes.SessionChatRemovedAction: - for i := range state.Chats { - if state.Chats[i].Resource == a.Chat { - state.Chats = append(state.Chats[:i], state.Chats[i+1:]...) - if state.DefaultChat != nil && *state.DefaultChat == a.Chat { - state.DefaultChat = nil - } - return ReduceOutcomeApplied - } - } - return ReduceOutcomeNoOp - case *ahptypes.SessionChatUpdatedAction: - for i := range state.Chats { - if state.Chats[i].Resource == a.Chat { - mergeChatSummaryPartial(&state.Chats[i], a.Changes) - return ReduceOutcomeApplied - } - } - return ReduceOutcomeNoOp - case *ahptypes.SessionDefaultChatChangedAction: - state.DefaultChat = a.DefaultChat - return ReduceOutcomeApplied - case *ahptypes.SessionTitleChangedAction: - state.Summary.Title = a.Title - touchSessionModified(state) - return ReduceOutcomeApplied case *ahptypes.SessionModelChangedAction: model := a.Model state.Summary.Model = &model - touchSessionModified(state) + touchModified(state) return ReduceOutcomeApplied case *ahptypes.SessionAgentChangedAction: state.Summary.Agent = a.Agent - touchSessionModified(state) + touchModified(state) return ReduceOutcomeApplied case *ahptypes.SessionIsReadChangedAction: state.Summary.Status = withStatusFlag(state.Summary.Status, ahptypes.SessionStatusIsRead, a.IsRead) @@ -718,7 +525,7 @@ func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAct for k, v := range a.Config { state.Config.Values[k] = v } - touchSessionModified(state) + touchModified(state) return ReduceOutcomeApplied case *ahptypes.SessionMetaChangedAction: state.Meta = a.Meta @@ -792,19 +599,134 @@ func ApplyActionToSession(state *ahptypes.SessionState, action ahptypes.StateAct return ReduceOutcomeNoOp case *ahptypes.SessionMcpServerStateChangedAction: return applyMcpServerStatusChanged(state, a) + case *ahptypes.SessionTruncatedAction: + return applyTruncated(state, a.TurnId) + case *ahptypes.SessionInputRequestedAction: + upsertInputRequest(state, a.Request) + return ReduceOutcomeApplied + case *ahptypes.SessionInputAnswerChangedAction: + return applyInputAnswerChanged(state, a) + case *ahptypes.SessionInputCompletedAction: + list := state.InputRequests + if list == nil { + return ReduceOutcomeNoOp + } + had := false + next := list[:0] + for _, r := range list { + if r.Id == a.RequestId { + had = true + continue + } + next = append(next, r) + } + if !had { + return ReduceOutcomeNoOp + } + if len(next) == 0 { + state.InputRequests = nil + } else { + state.InputRequests = next + } + refreshSummaryStatus(state) + touchModified(state) + return ReduceOutcomeApplied + case *ahptypes.SessionPendingMessageSetAction: + entry := ahptypes.PendingMessage{Id: a.Id, Message: a.Message} + switch a.Kind { + case ahptypes.PendingMessageKindSteering: + state.SteeringMessage = &entry + case ahptypes.PendingMessageKindQueued: + list := state.QueuedMessages + idx := -1 + for i := range list { + if list[i].Id == entry.Id { + idx = i + break + } + } + if idx >= 0 { + list[idx] = entry + } else { + list = append(list, entry) + } + state.QueuedMessages = list + } + return ReduceOutcomeApplied + case *ahptypes.SessionPendingMessageRemovedAction: + switch a.Kind { + case ahptypes.PendingMessageKindSteering: + if state.SteeringMessage != nil && state.SteeringMessage.Id == a.Id { + state.SteeringMessage = nil + return ReduceOutcomeApplied + } + return ReduceOutcomeNoOp + case ahptypes.PendingMessageKindQueued: + list := state.QueuedMessages + if list == nil { + return ReduceOutcomeNoOp + } + next := list[:0] + removed := false + for _, m := range list { + if m.Id == a.Id { + removed = true + continue + } + next = append(next, m) + } + if !removed { + return ReduceOutcomeNoOp + } + if len(next) == 0 { + state.QueuedMessages = nil + } else { + state.QueuedMessages = next + } + return ReduceOutcomeApplied + } + return ReduceOutcomeNoOp + case *ahptypes.SessionQueuedMessagesReorderedAction: + if state.QueuedMessages == nil { + return ReduceOutcomeNoOp + } + byID := make(map[string]ahptypes.PendingMessage, len(state.QueuedMessages)) + for _, m := range state.QueuedMessages { + byID[m.Id] = m + } + reordered := make([]ahptypes.PendingMessage, 0, len(byID)) + seen := make(map[string]struct{}, len(byID)) + for _, id := range a.Order { + if msg, ok := byID[id]; ok { + if _, dup := seen[id]; !dup { + seen[id] = struct{}{} + reordered = append(reordered, msg) + } + } + } + // Append messages absent from `order`, preserving their original + // relative order in state.QueuedMessages (mirrors the canonical + // TypeScript reducer in types/channels-session/reducer.ts). + for _, m := range state.QueuedMessages { + if _, in := seen[m.Id]; !in { + reordered = append(reordered, m) + } + } + state.QueuedMessages = reordered + return ReduceOutcomeApplied } return ReduceOutcomeOutOfScope } -func applyTurnStarted(state *ahptypes.ChatState, a *ahptypes.ChatTurnStartedAction) ReduceOutcome { +func applyTurnStarted(state *ahptypes.SessionState, a *ahptypes.SessionTurnStartedAction) ReduceOutcome { state.ActiveTurn = &ahptypes.ActiveTurn{ Id: a.TurnId, Message: a.Message, ResponseParts: []ahptypes.ResponsePart{}, } - state.Status = summaryStatus(state, nil) - touchChatModified(state) - state.Status = withStatusFlag(state.Status, ahptypes.SessionStatusIsRead, false) + state.Summary.Status = summaryStatus(state, nil) + touchModified(state) + state.Summary.Status = withStatusFlag(state.Summary.Status, ahptypes.SessionStatusIsRead, false) if a.QueuedMessageId != nil { qmid := *a.QueuedMessageId @@ -829,7 +751,7 @@ func applyTurnStarted(state *ahptypes.ChatState, a *ahptypes.ChatTurnStartedActi return ReduceOutcomeApplied } -func applyToolCallDelta(state *ahptypes.ChatState, a *ahptypes.ChatToolCallDeltaAction) ReduceOutcome { +func applyToolCallDelta(state *ahptypes.SessionState, a *ahptypes.SessionToolCallDeltaAction) ReduceOutcome { return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState { s, ok := tc.Value.(*ahptypes.ToolCallStreamingState) if !ok { @@ -852,7 +774,7 @@ func applyToolCallDelta(state *ahptypes.ChatState, a *ahptypes.ChatToolCallDelta }) } -func applyToolCallReady(state *ahptypes.ChatState, a *ahptypes.ChatToolCallReadyAction) ReduceOutcome { +func applyToolCallReady(state *ahptypes.SessionState, a *ahptypes.SessionToolCallReadyAction) ReduceOutcome { return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState { common := toolCallMeta(tc) if a.Meta != nil { @@ -905,7 +827,7 @@ func resolveSelectedOption(options []ahptypes.ConfirmationOption, id *string) *a return nil } -func applyToolCallConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToolCallConfirmedAction) ReduceOutcome { +func applyToolCallConfirmed(state *ahptypes.SessionState, a *ahptypes.SessionToolCallConfirmedAction) ReduceOutcome { return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState { s, ok := tc.Value.(*ahptypes.ToolCallPendingConfirmationState) if !ok { @@ -963,7 +885,7 @@ func applyToolCallConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToolCallC }) } -func applyToolCallComplete(state *ahptypes.ChatState, a *ahptypes.ChatToolCallCompleteAction) ReduceOutcome { +func applyToolCallComplete(state *ahptypes.SessionState, a *ahptypes.SessionToolCallCompleteAction) ReduceOutcome { return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState { common := toolCallMeta(tc) if a.Meta != nil { @@ -1027,7 +949,7 @@ func applyToolCallComplete(state *ahptypes.ChatState, a *ahptypes.ChatToolCallCo }) } -func applyToolCallResultConfirmed(state *ahptypes.ChatState, a *ahptypes.ChatToolCallResultConfirmedAction) ReduceOutcome { +func applyToolCallResultConfirmed(state *ahptypes.SessionState, a *ahptypes.SessionToolCallResultConfirmedAction) ReduceOutcome { return updateToolCall(state, a.TurnId, a.ToolCallId, func(tc ahptypes.ToolCallState) ahptypes.ToolCallState { s, ok := tc.Value.(*ahptypes.ToolCallPendingResultConfirmationState) if !ok { @@ -1115,7 +1037,7 @@ func applyMcpServerStatusChanged(state *ahptypes.SessionState, a *ahptypes.Sessi return ReduceOutcomeNoOp } -func applyTruncated(state *ahptypes.ChatState, turnID *string) ReduceOutcome { +func applyTruncated(state *ahptypes.SessionState, turnID *string) ReduceOutcome { if turnID == nil { state.Turns = []ahptypes.Turn{} } else { @@ -1133,12 +1055,12 @@ func applyTruncated(state *ahptypes.ChatState, turnID *string) ReduceOutcome { } state.ActiveTurn = nil state.InputRequests = nil - touchChatModified(state) - state.Status = summaryStatus(state, nil) + touchModified(state) + state.Summary.Status = summaryStatus(state, nil) return ReduceOutcomeApplied } -func applyInputAnswerChanged(state *ahptypes.ChatState, a *ahptypes.ChatInputAnswerChangedAction) ReduceOutcome { +func applyInputAnswerChanged(state *ahptypes.SessionState, a *ahptypes.SessionInputAnswerChangedAction) ReduceOutcome { list := state.InputRequests idx := -1 for i := range list { @@ -1152,7 +1074,7 @@ func applyInputAnswerChanged(state *ahptypes.ChatState, a *ahptypes.ChatInputAns } req := &list[idx] if req.Answers == nil { - req.Answers = make(map[string]ahptypes.ChatInputAnswer) + req.Answers = make(map[string]ahptypes.SessionInputAnswer) } if a.Answer == nil { delete(req.Answers, a.QuestionId) @@ -1162,7 +1084,7 @@ func applyInputAnswerChanged(state *ahptypes.ChatState, a *ahptypes.ChatInputAns if len(req.Answers) == 0 { req.Answers = nil } - touchChatModified(state) + touchModified(state) return ReduceOutcomeApplied } diff --git a/clients/go/ahp/reducers_fixture_test.go b/clients/go/ahp/reducers_fixture_test.go index b74bdb9c..0d64fddd 100644 --- a/clients/go/ahp/reducers_fixture_test.go +++ b/clients/go/ahp/reducers_fixture_test.go @@ -91,11 +91,10 @@ var reducerFixturesSkipList = map[string]string{ func TestFixtureDrivenReducerParity(t *testing.T) { dir := findFixtureDir(t) - // Use a deterministic timestamp so modifiedAt matches the TypeScript - // reference reducer: Date.now() === 9999, so chat timestamps become - // "1970-01-01T00:00:09.999Z". - const mockNowMillis int64 = 9999 - SetNowProvider(func() int64 { return mockNowMillis }) + // Use a deterministic timestamp so summary.modifiedAt matches + // what the TypeScript reference reducer stamps in fixtures. + const mockNow int64 = 9999 + SetNowProvider(func() int64 { return mockNow }) t.Cleanup(func() { SetNowProvider(nil) }) entries, err := os.ReadDir(dir) @@ -150,8 +149,6 @@ func TestFixtureDrivenReducerParity(t *testing.T) { runFixture[ahptypes.RootState](tt, fixture.Initial, fixture.Expected, actions, ApplyActionToRoot) case "session": runFixture[ahptypes.SessionState](tt, fixture.Initial, fixture.Expected, actions, ApplyActionToSession) - case "chat": - runFixture[ahptypes.ChatState](tt, fixture.Initial, fixture.Expected, actions, ApplyActionToChat) case "terminal": runFixture[ahptypes.TerminalState](tt, fixture.Initial, fixture.Expected, actions, ApplyActionToTerminal) case "changeset": diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go index ac0e1417..6c5a0263 100644 --- a/clients/go/ahptypes/actions.generated.go +++ b/clients/go/ahptypes/actions.generated.go @@ -23,43 +23,39 @@ const ( ActionTypeRootActiveSessionsChanged ActionType = "root/activeSessionsChanged" ActionTypeSessionReady ActionType = "session/ready" ActionTypeSessionCreationFailed ActionType = "session/creationFailed" - ActionTypeSessionChatAdded ActionType = "session/chatAdded" - ActionTypeSessionChatRemoved ActionType = "session/chatRemoved" - ActionTypeSessionChatUpdated ActionType = "session/chatUpdated" - ActionTypeSessionDefaultChatChanged ActionType = "session/defaultChatChanged" - ActionTypeChatTurnStarted ActionType = "chat/turnStarted" - ActionTypeChatDelta ActionType = "chat/delta" - ActionTypeChatResponsePart ActionType = "chat/responsePart" - ActionTypeChatToolCallStart ActionType = "chat/toolCallStart" - ActionTypeChatToolCallDelta ActionType = "chat/toolCallDelta" - ActionTypeChatToolCallReady ActionType = "chat/toolCallReady" - ActionTypeChatToolCallConfirmed ActionType = "chat/toolCallConfirmed" - ActionTypeChatToolCallComplete ActionType = "chat/toolCallComplete" - ActionTypeChatToolCallResultConfirmed ActionType = "chat/toolCallResultConfirmed" - ActionTypeChatToolCallContentChanged ActionType = "chat/toolCallContentChanged" - ActionTypeChatTurnComplete ActionType = "chat/turnComplete" - ActionTypeChatTurnCancelled ActionType = "chat/turnCancelled" - ActionTypeChatError ActionType = "chat/error" + ActionTypeSessionTurnStarted ActionType = "session/turnStarted" + ActionTypeSessionDelta ActionType = "session/delta" + ActionTypeSessionResponsePart ActionType = "session/responsePart" + ActionTypeSessionToolCallStart ActionType = "session/toolCallStart" + ActionTypeSessionToolCallDelta ActionType = "session/toolCallDelta" + ActionTypeSessionToolCallReady ActionType = "session/toolCallReady" + ActionTypeSessionToolCallConfirmed ActionType = "session/toolCallConfirmed" + ActionTypeSessionToolCallComplete ActionType = "session/toolCallComplete" + ActionTypeSessionToolCallResultConfirmed ActionType = "session/toolCallResultConfirmed" + ActionTypeSessionToolCallContentChanged ActionType = "session/toolCallContentChanged" + ActionTypeSessionTurnComplete ActionType = "session/turnComplete" + ActionTypeSessionTurnCancelled ActionType = "session/turnCancelled" + ActionTypeSessionError ActionType = "session/error" ActionTypeSessionTitleChanged ActionType = "session/titleChanged" - ActionTypeChatUsage ActionType = "chat/usage" - ActionTypeChatReasoning ActionType = "chat/reasoning" + ActionTypeSessionUsage ActionType = "session/usage" + ActionTypeSessionReasoning ActionType = "session/reasoning" ActionTypeSessionModelChanged ActionType = "session/modelChanged" ActionTypeSessionAgentChanged ActionType = "session/agentChanged" ActionTypeSessionServerToolsChanged ActionType = "session/serverToolsChanged" ActionTypeSessionActiveClientChanged ActionType = "session/activeClientChanged" ActionTypeSessionActiveClientToolsChanged ActionType = "session/activeClientToolsChanged" - ActionTypeChatPendingMessageSet ActionType = "chat/pendingMessageSet" - ActionTypeChatPendingMessageRemoved ActionType = "chat/pendingMessageRemoved" - ActionTypeChatQueuedMessagesReordered ActionType = "chat/queuedMessagesReordered" - ActionTypeChatInputRequested ActionType = "chat/inputRequested" - ActionTypeChatInputAnswerChanged ActionType = "chat/inputAnswerChanged" - ActionTypeChatInputCompleted ActionType = "chat/inputCompleted" + ActionTypeSessionPendingMessageSet ActionType = "session/pendingMessageSet" + ActionTypeSessionPendingMessageRemoved ActionType = "session/pendingMessageRemoved" + ActionTypeSessionQueuedMessagesReordered ActionType = "session/queuedMessagesReordered" + ActionTypeSessionInputRequested ActionType = "session/inputRequested" + ActionTypeSessionInputAnswerChanged ActionType = "session/inputAnswerChanged" + ActionTypeSessionInputCompleted ActionType = "session/inputCompleted" ActionTypeSessionCustomizationsChanged ActionType = "session/customizationsChanged" ActionTypeSessionCustomizationToggled ActionType = "session/customizationToggled" ActionTypeSessionCustomizationUpdated ActionType = "session/customizationUpdated" ActionTypeSessionCustomizationRemoved ActionType = "session/customizationRemoved" ActionTypeSessionMcpServerStateChanged ActionType = "session/mcpServerStateChanged" - ActionTypeChatTruncated ActionType = "chat/truncated" + ActionTypeSessionTruncated ActionType = "session/truncated" ActionTypeSessionIsReadChanged ActionType = "session/isReadChanged" ActionTypeSessionIsArchivedChanged ActionType = "session/isArchivedChanged" ActionTypeSessionActivityChanged ActionType = "session/activityChanged" @@ -152,56 +148,10 @@ type SessionCreationFailedAction struct { Error ErrorInfo `json:"error"` } -// A chat was added to this session's catalog. Upsert semantics: if a chat -// with the same `summary.resource` already exists, the existing entry is -// replaced. -// -// Mirrors the root-channel `root/sessionAdded` notification. -type SessionChatAddedAction struct { - Type ActionType `json:"type"` - // The full summary of the newly added (or upserted) chat. - Summary ChatSummary `json:"summary"` -} - -// A chat was removed from this session's catalog. No-op when no entry matches. -// -// Mirrors the root-channel `root/sessionRemoved` notification. -type SessionChatRemovedAction struct { - Type ActionType `json:"type"` - // The URI of the chat to remove. - Chat URI `json:"chat"` -} - -// One existing chat's summary fields changed. -// -// Partial-update semantics: only fields present in `changes` are written; -// omitted fields are preserved. Identity fields (`resource`) MUST NOT be -// carried in `changes`. No-op when no entry with `chat` exists — clients -// SHOULD then wait for a {@link SessionChatAddedAction | `session/chatAdded`}. -// -// Mirrors the root-channel `root/sessionSummaryChanged` notification. -type SessionChatUpdatedAction struct { - Type ActionType `json:"type"` - // The URI of the chat whose summary changed. - Chat URI `json:"chat"` - // Mutable summary fields that changed; omitted fields are unchanged. - // - // Identity fields (`resource`) never change and MUST be omitted by - // senders; receivers SHOULD ignore them if present. - Changes PartialChatSummary `json:"changes"` -} - -// The default chat input-routing hint for this session changed. -type SessionDefaultChatChangedAction struct { - Type ActionType `json:"type"` - // New default chat URI, or `undefined` to clear the hint. - DefaultChat *URI `json:"defaultChat,omitempty"` -} - // A new message has been sent to the agent, and a new turn starts. // // A client is only allowed to send {@link MessageKind.User} messages. -type ChatTurnStartedAction struct { +type SessionTurnStartedAction struct { Type ActionType `json:"type"` // Turn identifier TurnId string `json:"turnId"` @@ -213,9 +163,9 @@ type ChatTurnStartedAction struct { // Streaming text chunk from the assistant, appended to a specific response part. // -// The server MUST first emit a `chat/responsePart` to create the target +// The server MUST first emit a `session/responsePart` to create the target // part (markdown or reasoning), then use this action to append text to it. -type ChatDeltaAction struct { +type SessionDeltaAction struct { Type ActionType `json:"type"` // Turn identifier TurnId string `json:"turnId"` @@ -226,7 +176,7 @@ type ChatDeltaAction struct { } // Structured content appended to the response. -type ChatResponsePartAction struct { +type SessionResponsePartAction struct { Type ActionType `json:"type"` // Turn identifier TurnId string `json:"turnId"` @@ -239,9 +189,9 @@ type ChatResponsePartAction struct { // The server sets {@link ToolCallContributor | `contributor`} to identify // the origin of the tool. For client-provided tools, the named client is // responsible for executing the tool once it reaches the `running` state -// and dispatching `chat/toolCallComplete`. For MCP-served tools, the +// and dispatching `session/toolCallComplete`. For MCP-served tools, the // server executes the call against the named `McpServerCustomization`. -type ChatToolCallStartAction struct { +type SessionToolCallStartAction struct { // Turn identifier TurnId string `json:"turnId"` // Tool call identifier @@ -264,7 +214,7 @@ type ChatToolCallStartAction struct { } // Streaming partial parameters for a tool call. -type ChatToolCallDeltaAction struct { +type SessionToolCallDeltaAction struct { // Turn identifier TurnId string `json:"turnId"` // Tool call identifier @@ -291,12 +241,12 @@ type ChatToolCallDeltaAction struct { // When dispatched for a `running` tool call (e.g. mid-execution permission needed), // transitions back to `pending-confirmation`. The `invocationMessage` and `_meta` // SHOULD be updated to describe the specific confirmation needed. Clients use the -// standard `chat/toolCallConfirmed` flow to approve or deny. +// standard `session/toolCallConfirmed` flow to approve or deny. // // For client-provided tools, the server typically sets `confirmed` to // `'not-needed'` so the tool transitions directly to `running`, where the // owning client can begin execution immediately. -type ChatToolCallReadyAction struct { +type SessionToolCallReadyAction struct { // Turn identifier TurnId string `json:"turnId"` // Tool call identifier @@ -328,9 +278,9 @@ type ChatToolCallReadyAction struct { Options []ConfirmationOption `json:"options,omitempty"` } -// ChatToolCallConfirmedAction is the client approves or denies a +// SessionToolCallConfirmedAction is the client approves or denies a // pending tool call (merged approved + denied variants on the wire). -type ChatToolCallConfirmedAction struct { +type SessionToolCallConfirmedAction struct { Type ActionType `json:"type"` TurnId string `json:"turnId"` ToolCallId string `json:"toolCallId"` @@ -354,7 +304,7 @@ type ChatToolCallConfirmedAction struct { // Servers waiting on a client tool call MAY time out after a reasonable duration // if the implementing client disconnects or becomes unresponsive, and dispatch // this action with `result.success = false` and an appropriate error. -type ChatToolCallCompleteAction struct { +type SessionToolCallCompleteAction struct { // Turn identifier TurnId string `json:"turnId"` // Tool call identifier @@ -376,7 +326,7 @@ type ChatToolCallCompleteAction struct { // Client approves or denies a tool's result. // // If `approved` is `false`, the tool transitions to `cancelled` with reason `result-denied`. -type ChatToolCallResultConfirmedAction struct { +type SessionToolCallResultConfirmedAction struct { // Turn identifier TurnId string `json:"turnId"` // Tool call identifier @@ -393,49 +343,22 @@ type ChatToolCallResultConfirmedAction struct { Approved bool `json:"approved"` } -// Partial content produced while a tool is still executing. -// -// Replaces the `content` array on the running tool call state. Clients can -// use this to display live feedback (e.g. a terminal reference) before the -// tool completes. -// -// For client-provided tools (where `toolClientId` is set on the tool call state), -// the owning client dispatches this action to stream intermediate content while -// executing. The server SHOULD reject this action if the dispatching client does -// not match `toolClientId`. -type ChatToolCallContentChangedAction struct { - // Turn identifier - TurnId string `json:"turnId"` - // Tool call identifier - ToolCallId string `json:"toolCallId"` - // Additional provider-specific metadata for this tool call. - // - // Clients MAY look for well-known keys here to provide enhanced UI. - // For example, a `ptyTerminal` key with `{ input: string; output: string }` - // indicates the tool operated on a terminal (both `input` and `output` may - // contain escape sequences). - Meta map[string]json.RawMessage `json:"_meta,omitempty"` - Type ActionType `json:"type"` - // The current partial content for the running tool call - Content []ToolResultContent `json:"content"` -} - // Turn finished — the assistant is idle. -type ChatTurnCompleteAction struct { +type SessionTurnCompleteAction struct { Type ActionType `json:"type"` // Turn identifier TurnId string `json:"turnId"` } // Turn was aborted; server stops processing. -type ChatTurnCancelledAction struct { +type SessionTurnCancelledAction struct { Type ActionType `json:"type"` // Turn identifier TurnId string `json:"turnId"` } // Error during turn processing. -type ChatErrorAction struct { +type SessionErrorAction struct { Type ActionType `json:"type"` // Turn identifier TurnId string `json:"turnId"` @@ -452,7 +375,7 @@ type SessionTitleChangedAction struct { } // Token usage report for a turn. -type ChatUsageAction struct { +type SessionUsageAction struct { Type ActionType `json:"type"` // Turn identifier TurnId string `json:"turnId"` @@ -462,9 +385,9 @@ type ChatUsageAction struct { // Reasoning/thinking text from the model, appended to a specific reasoning response part. // -// The server MUST first emit a `chat/responsePart` to create the target +// The server MUST first emit a `session/responsePart` to create the target // reasoning part, then use this action to append text to it. -type ChatReasoningAction struct { +type SessionReasoningAction struct { Type ActionType `json:"type"` // Turn identifier TurnId string `json:"turnId"` @@ -474,104 +397,6 @@ type ChatReasoningAction struct { Content string `json:"content"` } -// A pending message was set (upsert semantics: creates or replaces). -// -// For steering messages, this always replaces the single steering message. -// For queued messages, if a message with the given `id` already exists it is -// updated in place; otherwise it is appended to the queue. If the chat is -// idle when a queued message is set, the server SHOULD immediately consume it -// and start a new turn. -// -// A client is only allowed to send {@link MessageKind.User} messages. -type ChatPendingMessageSetAction struct { - Type ActionType `json:"type"` - // Whether this is a steering or queued message - Kind PendingMessageKind `json:"kind"` - // Unique identifier for this pending message - Id string `json:"id"` - // The message content - Message Message `json:"message"` -} - -// A pending message was removed (steering or queued). -// -// Dispatched by clients to cancel a pending message, or by the server when -// it consumes a message (e.g. starting a turn from a queued message or -// injecting a steering message into the current turn). -type ChatPendingMessageRemovedAction struct { - Type ActionType `json:"type"` - // Whether this is a steering or queued message - Kind PendingMessageKind `json:"kind"` - // Identifier of the pending message to remove - Id string `json:"id"` -} - -// Reorder the queued messages. -// -// The `order` array contains the IDs of queued messages in their new -// desired order. IDs not present in the current queue are ignored. -// Queued messages whose IDs are absent from `order` are appended at -// the end in their original relative order (so a client with a stale -// view of the queue never silently drops messages). -type ChatQueuedMessagesReorderedAction struct { - Type ActionType `json:"type"` - // Queued message IDs in the desired order - Order []string `json:"order"` -} - -// A session requested input from the user. -// -// Full-request upsert semantics: the `request` replaces any existing request -// with the same `id`, or is appended if it is new. Answer drafts are preserved -// unless `request.answers` is provided. -type ChatInputRequestedAction struct { - Type ActionType `json:"type"` - // Input request to create or replace - Request ChatInputRequest `json:"request"` -} - -// A client updated, submitted, skipped, or removed a single in-progress answer. -// -// Dispatching with `answer: undefined` removes that question's answer draft. -type ChatInputAnswerChangedAction struct { - Type ActionType `json:"type"` - // Input request identifier - RequestId string `json:"requestId"` - // Question identifier within the input request - QuestionId string `json:"questionId"` - // Updated answer, or `undefined` to clear an answer draft - Answer *ChatInputAnswer `json:"answer,omitempty"` -} - -// A client accepted, declined, or cancelled a session input request. -// -// If accepted, the server uses `answers` (when provided) plus the request's -// synced answer state to resume the blocked operation. -type ChatInputCompletedAction struct { - Type ActionType `json:"type"` - // Input request identifier - RequestId string `json:"requestId"` - // Completion outcome - Response ChatInputResponseKind `json:"response"` - // Optional final answer replacement, keyed by question ID - Answers map[string]ChatInputAnswer `json:"answers,omitempty"` -} - -// Truncates a session's history. If `turnId` is provided, all turns after that -// turn are removed and the specified turn is kept. If `turnId` is omitted, all -// turns are removed. -// -// If there is an active turn it is silently dropped and the chat status -// returns to `idle`. -// -// Common use-case: truncate old data then dispatch a new -// `chat/turnStarted` with an edited message. -type ChatTruncatedAction struct { - Type ActionType `json:"type"` - // Keep turns up to and including this turn. Omit to clear all turns. - TurnId *string `json:"turnId,omitempty"` -} - // Model changed for this session. type SessionModelChangedAction struct { Type ActionType `json:"type"` @@ -672,6 +497,89 @@ type SessionActiveClientToolsChangedAction struct { Tools []ToolDefinition `json:"tools"` } +// A pending message was set (upsert semantics: creates or replaces). +// +// For steering messages, this always replaces the single steering message. +// For queued messages, if a message with the given `id` already exists it is +// updated in place; otherwise it is appended to the queue. If the session is +// idle when a queued message is set, the server SHOULD immediately consume it +// and start a new turn. +// +// A client is only allowed to send {@link MessageKind.User} messages. +type SessionPendingMessageSetAction struct { + Type ActionType `json:"type"` + // Whether this is a steering or queued message + Kind PendingMessageKind `json:"kind"` + // Unique identifier for this pending message + Id string `json:"id"` + // The message content + Message Message `json:"message"` +} + +// A pending message was removed (steering or queued). +// +// Dispatched by clients to cancel a pending message, or by the server when +// it consumes a message (e.g. starting a turn from a queued message or +// injecting a steering message into the current turn). +type SessionPendingMessageRemovedAction struct { + Type ActionType `json:"type"` + // Whether this is a steering or queued message + Kind PendingMessageKind `json:"kind"` + // Identifier of the pending message to remove + Id string `json:"id"` +} + +// Reorder the queued messages. +// +// The `order` array contains the IDs of queued messages in their new +// desired order. IDs not present in the current queue are ignored. +// Queued messages whose IDs are absent from `order` are appended at +// the end in their original relative order (so a client with a stale +// view of the queue never silently drops messages). +type SessionQueuedMessagesReorderedAction struct { + Type ActionType `json:"type"` + // Queued message IDs in the desired order + Order []string `json:"order"` +} + +// A session requested input from the user. +// +// Full-request upsert semantics: the `request` replaces any existing request +// with the same `id`, or is appended if it is new. Answer drafts are preserved +// unless `request.answers` is provided. +type SessionInputRequestedAction struct { + Type ActionType `json:"type"` + // Input request to create or replace + Request SessionInputRequest `json:"request"` +} + +// A client updated, submitted, skipped, or removed a single in-progress answer. +// +// Dispatching with `answer: undefined` removes that question's answer draft. +type SessionInputAnswerChangedAction struct { + Type ActionType `json:"type"` + // Input request identifier + RequestId string `json:"requestId"` + // Question identifier within the input request + QuestionId string `json:"questionId"` + // Updated answer, or `undefined` to clear an answer draft + Answer *SessionInputAnswer `json:"answer,omitempty"` +} + +// A client accepted, declined, or cancelled a session input request. +// +// If accepted, the server uses `answers` (when provided) plus the request's +// synced answer state to resume the blocked operation. +type SessionInputCompletedAction struct { + Type ActionType `json:"type"` + // Input request identifier + RequestId string `json:"requestId"` + // Completion outcome + Response SessionInputResponseKind `json:"response"` + // Optional final answer replacement, keyed by question ID + Answers map[string]SessionInputAnswer `json:"answers,omitempty"` +} + // The session's customizations have changed. // // Full-replacement semantics: the `customizations` array replaces the @@ -753,6 +661,21 @@ type SessionMcpServerStateChangedAction struct { Channel *URI `json:"channel,omitempty"` } +// Truncates a session's history. If `turnId` is provided, all turns after that +// turn are removed and the specified turn is kept. If `turnId` is omitted, all +// turns are removed. +// +// If there is an active turn it is silently dropped and the session status +// returns to `idle`. +// +// Common use-case: truncate old data then dispatch a new +// `session/turnStarted` with an edited message. +type SessionTruncatedAction struct { + Type ActionType `json:"type"` + // Keep turns up to and including this turn. Omit to clear all turns. + TurnId *string `json:"turnId,omitempty"` +} + // Client changed a mutable config value mid-session. // // Only properties with `sessionMutable: true` in the config schema may be @@ -775,6 +698,33 @@ type SessionMetaChangedAction struct { Meta map[string]json.RawMessage `json:"_meta,omitempty"` } +// Partial content produced while a tool is still executing. +// +// Replaces the `content` array on the running tool call state. Clients can +// use this to display live feedback (e.g. a terminal reference) before the +// tool completes. +// +// For client-provided tools (where `toolClientId` is set on the tool call state), +// the owning client dispatches this action to stream intermediate content while +// executing. The server SHOULD reject this action if the dispatching client does +// not match `toolClientId`. +type SessionToolCallContentChangedAction struct { + // Turn identifier + TurnId string `json:"turnId"` + // Tool call identifier + ToolCallId string `json:"toolCallId"` + // Additional provider-specific metadata for this tool call. + // + // Clients MAY look for well-known keys here to provide enhanced UI. + // For example, a `ptyTerminal` key with `{ input: string; output: string }` + // indicates the tool operated on a terminal (both `input` and `output` may + // contain escape sequences). + Meta map[string]json.RawMessage `json:"_meta,omitempty"` + Type ActionType `json:"type"` + // The current partial content for the running tool call + Content []ToolResultContent `json:"content"` +} + // The {@link ChangesetState.status} for this changeset transitioned (e.g. // `computing → ready`). The error payload is set together with `status` // whenever it transitions to {@link ChangesetStatus.Error | Error}. @@ -1106,33 +1056,21 @@ func (*RootActiveSessionsChangedAction) isStateAction() {} func (*RootConfigChangedAction) isStateAction() {} func (*SessionReadyAction) isStateAction() {} func (*SessionCreationFailedAction) isStateAction() {} -func (*SessionChatAddedAction) isStateAction() {} -func (*SessionChatRemovedAction) isStateAction() {} -func (*SessionChatUpdatedAction) isStateAction() {} -func (*SessionDefaultChatChangedAction) isStateAction() {} -func (*ChatTurnStartedAction) isStateAction() {} -func (*ChatDeltaAction) isStateAction() {} -func (*ChatResponsePartAction) isStateAction() {} -func (*ChatToolCallStartAction) isStateAction() {} -func (*ChatToolCallDeltaAction) isStateAction() {} -func (*ChatToolCallReadyAction) isStateAction() {} -func (*ChatToolCallConfirmedAction) isStateAction() {} -func (*ChatToolCallCompleteAction) isStateAction() {} -func (*ChatToolCallResultConfirmedAction) isStateAction() {} -func (*ChatToolCallContentChangedAction) isStateAction() {} -func (*ChatTurnCompleteAction) isStateAction() {} -func (*ChatTurnCancelledAction) isStateAction() {} -func (*ChatErrorAction) isStateAction() {} +func (*SessionTurnStartedAction) isStateAction() {} +func (*SessionDeltaAction) isStateAction() {} +func (*SessionResponsePartAction) isStateAction() {} +func (*SessionToolCallStartAction) isStateAction() {} +func (*SessionToolCallDeltaAction) isStateAction() {} +func (*SessionToolCallReadyAction) isStateAction() {} +func (*SessionToolCallConfirmedAction) isStateAction() {} +func (*SessionToolCallCompleteAction) isStateAction() {} +func (*SessionToolCallResultConfirmedAction) isStateAction() {} +func (*SessionTurnCompleteAction) isStateAction() {} +func (*SessionTurnCancelledAction) isStateAction() {} +func (*SessionErrorAction) isStateAction() {} func (*SessionTitleChangedAction) isStateAction() {} -func (*ChatUsageAction) isStateAction() {} -func (*ChatReasoningAction) isStateAction() {} -func (*ChatPendingMessageSetAction) isStateAction() {} -func (*ChatPendingMessageRemovedAction) isStateAction() {} -func (*ChatQueuedMessagesReorderedAction) isStateAction() {} -func (*ChatInputRequestedAction) isStateAction() {} -func (*ChatInputAnswerChangedAction) isStateAction() {} -func (*ChatInputCompletedAction) isStateAction() {} -func (*ChatTruncatedAction) isStateAction() {} +func (*SessionUsageAction) isStateAction() {} +func (*SessionReasoningAction) isStateAction() {} func (*SessionModelChangedAction) isStateAction() {} func (*SessionAgentChangedAction) isStateAction() {} func (*SessionIsReadChangedAction) isStateAction() {} @@ -1142,13 +1080,21 @@ func (*SessionChangesetsChangedAction) isStateAction() {} func (*SessionServerToolsChangedAction) isStateAction() {} func (*SessionActiveClientChangedAction) isStateAction() {} func (*SessionActiveClientToolsChangedAction) isStateAction() {} +func (*SessionPendingMessageSetAction) isStateAction() {} +func (*SessionPendingMessageRemovedAction) isStateAction() {} +func (*SessionQueuedMessagesReorderedAction) isStateAction() {} +func (*SessionInputRequestedAction) isStateAction() {} +func (*SessionInputAnswerChangedAction) isStateAction() {} +func (*SessionInputCompletedAction) isStateAction() {} func (*SessionCustomizationsChangedAction) isStateAction() {} func (*SessionCustomizationToggledAction) isStateAction() {} func (*SessionCustomizationUpdatedAction) isStateAction() {} func (*SessionCustomizationRemovedAction) isStateAction() {} func (*SessionMcpServerStateChangedAction) isStateAction() {} +func (*SessionTruncatedAction) isStateAction() {} func (*SessionConfigChangedAction) isStateAction() {} func (*SessionMetaChangedAction) isStateAction() {} +func (*SessionToolCallContentChangedAction) isStateAction() {} func (*ChangesetStatusChangedAction) isStateAction() {} func (*ChangesetFileSetAction) isStateAction() {} func (*ChangesetFileRemovedAction) isStateAction() {} @@ -1218,104 +1164,74 @@ func (u *StateAction) UnmarshalJSON(data []byte) error { return err } u.Value = &value - case "session/chatAdded": - var value SessionChatAddedAction - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value - case "session/chatRemoved": - var value SessionChatRemovedAction - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value - case "session/chatUpdated": - var value SessionChatUpdatedAction - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value - case "session/defaultChatChanged": - var value SessionDefaultChatChangedAction - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value - case "chat/turnStarted": - var value ChatTurnStartedAction + case "session/turnStarted": + var value SessionTurnStartedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/delta": - var value ChatDeltaAction + case "session/delta": + var value SessionDeltaAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/responsePart": - var value ChatResponsePartAction + case "session/responsePart": + var value SessionResponsePartAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/toolCallStart": - var value ChatToolCallStartAction + case "session/toolCallStart": + var value SessionToolCallStartAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/toolCallDelta": - var value ChatToolCallDeltaAction + case "session/toolCallDelta": + var value SessionToolCallDeltaAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/toolCallReady": - var value ChatToolCallReadyAction + case "session/toolCallReady": + var value SessionToolCallReadyAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/toolCallConfirmed": - var value ChatToolCallConfirmedAction + case "session/toolCallConfirmed": + var value SessionToolCallConfirmedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/toolCallComplete": - var value ChatToolCallCompleteAction + case "session/toolCallComplete": + var value SessionToolCallCompleteAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/toolCallResultConfirmed": - var value ChatToolCallResultConfirmedAction + case "session/toolCallResultConfirmed": + var value SessionToolCallResultConfirmedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/toolCallContentChanged": - var value ChatToolCallContentChangedAction + case "session/turnComplete": + var value SessionTurnCompleteAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/turnComplete": - var value ChatTurnCompleteAction + case "session/turnCancelled": + var value SessionTurnCancelledAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/turnCancelled": - var value ChatTurnCancelledAction - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value - case "chat/error": - var value ChatErrorAction + case "session/error": + var value SessionErrorAction if err := json.Unmarshal(data, &value); err != nil { return err } @@ -1326,110 +1242,104 @@ func (u *StateAction) UnmarshalJSON(data []byte) error { return err } u.Value = &value - case "chat/usage": - var value ChatUsageAction + case "session/usage": + var value SessionUsageAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/reasoning": - var value ChatReasoningAction + case "session/reasoning": + var value SessionReasoningAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/pendingMessageSet": - var value ChatPendingMessageSetAction - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value - case "chat/pendingMessageRemoved": - var value ChatPendingMessageRemovedAction + case "session/modelChanged": + var value SessionModelChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/queuedMessagesReordered": - var value ChatQueuedMessagesReorderedAction + case "session/agentChanged": + var value SessionAgentChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/inputRequested": - var value ChatInputRequestedAction + case "session/isReadChanged": + var value SessionIsReadChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/inputAnswerChanged": - var value ChatInputAnswerChangedAction + case "session/isArchivedChanged": + var value SessionIsArchivedChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/inputCompleted": - var value ChatInputCompletedAction + case "session/activityChanged": + var value SessionActivityChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "chat/truncated": - var value ChatTruncatedAction + case "session/changesetsChanged": + var value SessionChangesetsChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/modelChanged": - var value SessionModelChangedAction + case "session/serverToolsChanged": + var value SessionServerToolsChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/agentChanged": - var value SessionAgentChangedAction + case "session/activeClientChanged": + var value SessionActiveClientChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/isReadChanged": - var value SessionIsReadChangedAction + case "session/activeClientToolsChanged": + var value SessionActiveClientToolsChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/isArchivedChanged": - var value SessionIsArchivedChangedAction + case "session/pendingMessageSet": + var value SessionPendingMessageSetAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/activityChanged": - var value SessionActivityChangedAction + case "session/pendingMessageRemoved": + var value SessionPendingMessageRemovedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/changesetsChanged": - var value SessionChangesetsChangedAction + case "session/queuedMessagesReordered": + var value SessionQueuedMessagesReorderedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/serverToolsChanged": - var value SessionServerToolsChangedAction + case "session/inputRequested": + var value SessionInputRequestedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/activeClientChanged": - var value SessionActiveClientChangedAction + case "session/inputAnswerChanged": + var value SessionInputAnswerChangedAction if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value - case "session/activeClientToolsChanged": - var value SessionActiveClientToolsChangedAction + case "session/inputCompleted": + var value SessionInputCompletedAction if err := json.Unmarshal(data, &value); err != nil { return err } @@ -1464,6 +1374,12 @@ func (u *StateAction) UnmarshalJSON(data []byte) error { return err } u.Value = &value + case "session/truncated": + var value SessionTruncatedAction + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value case "session/configChanged": var value SessionConfigChangedAction if err := json.Unmarshal(data, &value); err != nil { @@ -1476,6 +1392,12 @@ func (u *StateAction) UnmarshalJSON(data []byte) error { return err } u.Value = &value + case "session/toolCallContentChanged": + var value SessionToolCallContentChangedAction + if err := json.Unmarshal(data, &value); err != nil { + return err + } + u.Value = &value case "changeset/statusChanged": var value ChangesetStatusChangedAction if err := json.Unmarshal(data, &value); err != nil { diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go index 9bf01aef..a48150c5 100644 --- a/clients/go/ahptypes/commands.generated.go +++ b/clients/go/ahptypes/commands.generated.go @@ -262,36 +262,6 @@ type DisposeSessionParams struct { Channel URI `json:"channel"` } -// Identifies a source chat and turn to fork from. -type ChatForkSource struct { - // URI of the existing chat to fork from - Chat URI `json:"chat"` - // Turn ID in the source chat; content up to and including this turn's response is copied - TurnId string `json:"turnId"` -} - -// Creates a new chat within a session. -type CreateChatParams struct { - // Channel URI this command targets. - Channel URI `json:"channel"` - // Chat URI (client-chosen, e.g. `ahp-chat:/`). - Chat URI `json:"chat"` - // Optional initial message for the new chat. - InitialMessage *Message `json:"initialMessage,omitempty"` - // Optional per-chat model override. - Model *ModelSelection `json:"model,omitempty"` - // Optional per-chat agent override. - Agent *AgentSelection `json:"agent,omitempty"` - // Optional source chat and turn to fork from. - Source *ChatForkSource `json:"source,omitempty"` -} - -// Disposes a chat and cleans up server-side resources. -type DisposeChatParams struct { - // Channel URI this command targets. - Channel URI `json:"channel"` -} - // Returns a list of session summaries. Used to populate session lists and sidebars. // // The session list is **not** part of the state tree because it can be arbitrarily @@ -648,7 +618,7 @@ type CreateResourceWatchResult struct { Channel URI `json:"channel"` } -// Fetches historical turns for a chat. Used for lazy loading of conversation +// Fetches historical turns for a session. Used for lazy loading of conversation // history. type FetchTurnsParams struct { // Channel URI this command targets. diff --git a/clients/go/ahptypes/common.go b/clients/go/ahptypes/common.go index 1d8f529f..eb063deb 100644 --- a/clients/go/ahptypes/common.go +++ b/clients/go/ahptypes/common.go @@ -69,28 +69,6 @@ type JSONObject = map[string]json.RawMessage // TypeScript `unknown` type). type AnyValue = json.RawMessage -// PartialChatSummary is the partial equivalent of ChatSummary — every field is optional for delta updates. -type PartialChatSummary struct { - // Chat URI. Ignored by session/chatUpdated reducers; chat identity never changes. - Resource *URI `json:"resource,omitempty"` - // Chat title - Title *string `json:"title,omitempty"` - // Current chat status (reuses SessionStatus shape) - Status *SessionStatus `json:"status,omitempty"` - // Human-readable description of what the chat is currently doing - Activity *string `json:"activity,omitempty"` - // Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - ModifiedAt *string `json:"modifiedAt,omitempty"` - // Optional per-chat model override (defaults to the session's model) - Model *ModelSelection `json:"model,omitempty"` - // Optional per-chat agent override (defaults to the session's agent) - Agent *AgentSelection `json:"agent,omitempty"` - // How this chat came into existence - Origin *ChatOrigin `json:"origin,omitempty"` - // Optional per-chat working directory. - WorkingDirectory *URI `json:"workingDirectory,omitempty"` -} - // ─── StringOrMarkdown ──────────────────────────────────────────────────── // StringOrMarkdown is a wire value that may be either a plain JSON diff --git a/clients/go/ahptypes/notifications.generated.go b/clients/go/ahptypes/notifications.generated.go index 19d04e14..f74c8b2f 100644 --- a/clients/go/ahptypes/notifications.generated.go +++ b/clients/go/ahptypes/notifications.generated.go @@ -182,10 +182,7 @@ type PartialSessionSummary struct { // Absent (`undefined`) means no custom agent is selected for this session // — the session uses the provider's default behavior. Agent *AgentSelection `json:"agent,omitempty"` - // The default working directory URI for this session. Individual chats - // MAY override via {@link ChatSummary.workingDirectory | their own - // `workingDirectory`}; this field acts as the fallback for any chat that - // does not. + // The working directory URI for this session WorkingDirectory *URI `json:"workingDirectory,omitempty"` // Aggregate summary of file changes associated with this session. Servers // may populate this to give clients a quick at-a-glance view of the diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 91d13128..c7b72ec1 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -24,6 +24,16 @@ const ( PolicyStateUnconfigured PolicyState = "unconfigured" ) +// Discriminant for pending message kinds. +type PendingMessageKind string + +const ( + // Injected into the current turn at a convenient point + PendingMessageKindSteering PendingMessageKind = "steering" + // Sent automatically as a new turn after the current turn finishes + PendingMessageKindQueued PendingMessageKind = "queued" +) + // Session initialization state. type SessionLifecycle string @@ -61,63 +71,45 @@ func (s SessionStatus) Has(other SessionStatus) bool { return s&other == other } // Or returns s combined with the flags in other. func (s SessionStatus) Or(other SessionStatus) SessionStatus { return s | other } -type ChatOriginKind string - -const ( - ChatOriginKindUser ChatOriginKind = "user" - ChatOriginKindFork ChatOriginKind = "fork" - ChatOriginKindTool ChatOriginKind = "tool" -) - -// Discriminant for pending message kinds. -type PendingMessageKind string - -const ( - // Injected into the current turn at a convenient point - PendingMessageKindSteering PendingMessageKind = "steering" - // Sent automatically as a new turn after the current turn finishes - PendingMessageKindQueued PendingMessageKind = "queued" -) - // Answer lifecycle state. -type ChatInputAnswerState string +type SessionInputAnswerState string const ( - ChatInputAnswerStateDraft ChatInputAnswerState = "draft" - ChatInputAnswerStateSubmitted ChatInputAnswerState = "submitted" - ChatInputAnswerStateSkipped ChatInputAnswerState = "skipped" + SessionInputAnswerStateDraft SessionInputAnswerState = "draft" + SessionInputAnswerStateSubmitted SessionInputAnswerState = "submitted" + SessionInputAnswerStateSkipped SessionInputAnswerState = "skipped" ) // Answer value kind. -type ChatInputAnswerValueKind string +type SessionInputAnswerValueKind string const ( - ChatInputAnswerValueKindText ChatInputAnswerValueKind = "text" - ChatInputAnswerValueKindNumber ChatInputAnswerValueKind = "number" - ChatInputAnswerValueKindBoolean ChatInputAnswerValueKind = "boolean" - ChatInputAnswerValueKindSelected ChatInputAnswerValueKind = "selected" - ChatInputAnswerValueKindSelectedMany ChatInputAnswerValueKind = "selected-many" + SessionInputAnswerValueKindText SessionInputAnswerValueKind = "text" + SessionInputAnswerValueKindNumber SessionInputAnswerValueKind = "number" + SessionInputAnswerValueKindBoolean SessionInputAnswerValueKind = "boolean" + SessionInputAnswerValueKindSelected SessionInputAnswerValueKind = "selected" + SessionInputAnswerValueKindSelectedMany SessionInputAnswerValueKind = "selected-many" ) // Question/input control kind. -type ChatInputQuestionKind string +type SessionInputQuestionKind string const ( - ChatInputQuestionKindText ChatInputQuestionKind = "text" - ChatInputQuestionKindNumber ChatInputQuestionKind = "number" - ChatInputQuestionKindInteger ChatInputQuestionKind = "integer" - ChatInputQuestionKindBoolean ChatInputQuestionKind = "boolean" - ChatInputQuestionKindSingleSelect ChatInputQuestionKind = "single-select" - ChatInputQuestionKindMultiSelect ChatInputQuestionKind = "multi-select" + SessionInputQuestionKindText SessionInputQuestionKind = "text" + SessionInputQuestionKindNumber SessionInputQuestionKind = "number" + SessionInputQuestionKindInteger SessionInputQuestionKind = "integer" + SessionInputQuestionKindBoolean SessionInputQuestionKind = "boolean" + SessionInputQuestionKindSingleSelect SessionInputQuestionKind = "single-select" + SessionInputQuestionKindMultiSelect SessionInputQuestionKind = "multi-select" ) // How a client completed an input request. -type ChatInputResponseKind string +type SessionInputResponseKind string const ( - ChatInputResponseKindAccept ChatInputResponseKind = "accept" - ChatInputResponseKindDecline ChatInputResponseKind = "decline" - ChatInputResponseKindCancel ChatInputResponseKind = "cancel" + SessionInputResponseKindAccept SessionInputResponseKind = "accept" + SessionInputResponseKindDecline SessionInputResponseKind = "decline" + SessionInputResponseKindCancel SessionInputResponseKind = "cancel" ) // How a turn ended. @@ -581,6 +573,18 @@ type ConfigSchema struct { Required []string `json:"required,omitempty"` } +// A message queued for future delivery to the agent. +// +// Steering messages are injected into the current turn mid-flight. +// Queued messages are automatically started as new turns after the +// current turn naturally finishes. +type PendingMessage struct { + // Unique identifier for this pending message + Id string `json:"id"` + // The message that will start the next turn + Message Message `json:"message"` +} + // Full state for a single session, loaded when a client subscribes to the session's URI. type SessionState struct { // Lightweight session metadata @@ -593,13 +597,16 @@ type SessionState struct { ServerTools []ToolDefinition `json:"serverTools,omitempty"` // The client currently providing tools and interactive capabilities to this session ActiveClient *SessionActiveClient `json:"activeClient,omitempty"` - // Catalog of chats in this session. - Chats []ChatSummary `json:"chats"` - // The chat that receives input when the user addresses the session without - // selecting a specific chat. This is a UI routing hint, not a hierarchy - // marker — chats remain equal peers at the protocol level. Hosts MAY change - // this over the session's lifetime. - DefaultChat *URI `json:"defaultChat,omitempty"` + // Completed turns + Turns []Turn `json:"turns"` + // Currently in-progress turn + ActiveTurn *ActiveTurn `json:"activeTurn,omitempty"` + // Message to inject into the current turn at a convenient point + SteeringMessage *PendingMessage `json:"steeringMessage,omitempty"` + // Messages to send automatically as new turns after the current turn finishes + QueuedMessages []PendingMessage `json:"queuedMessages,omitempty"` + // Requests for user input that are currently blocking or informing session progress + InputRequests []SessionInputRequest `json:"inputRequests,omitempty"` // Session configuration schema and current values Config *SessionConfigState `json:"config,omitempty"` // Top-level customizations active in this session. @@ -656,39 +663,6 @@ type SessionActiveClient struct { Customizations []ClientPluginCustomization `json:"customizations,omitempty"` } -// Lightweight catalog entry summarizing one session. Surfaced via -// {@link RootChannelCommands.listSessions | `root/listSessions`} and -// `root/sessionAdded`/`root/sessionSummaryChanged` notifications. -// -// **Aggregation across chats.** Once a session contains more than one chat, -// several `SessionSummary` fields are derived from the underlying -// {@link SessionState.chats | chat catalog}. Producers SHOULD follow these -// rules so clients that only consume the session summary (e.g. a session -// list) still see meaningful state: -// -// - `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` / -// `Error` — bits 0–4) from the -// {@link SessionState.defaultChat | default chat} when present, else from -// the most recently modified chat. **Promote** `InputNeeded` whenever any -// chat in the session needs input, and **promote** `Error` whenever any -// chat is in an error state — both override the default-chat bits. The -// orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped. -// - `activity`: mirror the activity string of the default chat, or of the -// chat currently driving the promoted status bits when a non-default chat -// wins (e.g. the chat that raised `InputNeeded`). -// - `modifiedAt`: the max of all chats' `modifiedAt`. -// - `model` / `agent`: the session-level selection. Per-chat overrides are -// surfaced on individual {@link ChatSummary} entries, not aggregated up. -// - `workingDirectory`: the session-level **default**. Individual chats MAY -// override via {@link ChatSummary.workingDirectory}; aggregating these up -// is meaningless and SHOULD NOT be attempted. -// - `changes`: optional roll-up across all chats. Producers MAY sum the -// per-chat changeset stats or report the most expensive chat's stats — -// whichever is cheaper for the host to compute. -// -// Sessions with a single chat trivially satisfy all of the above (the chat's -// values pass through unchanged). The rules only matter once a session -// carries multiple chats. type SessionSummary struct { // Session URI Resource URI `json:"resource"` @@ -713,10 +687,7 @@ type SessionSummary struct { // Absent (`undefined`) means no custom agent is selected for this session // — the session uses the provider's default behavior. Agent *AgentSelection `json:"agent,omitempty"` - // The default working directory URI for this session. Individual chats - // MAY override via {@link ChatSummary.workingDirectory | their own - // `workingDirectory`}; this field acts as the fallback for any chat that - // does not. + // The working directory URI for this session WorkingDirectory *URI `json:"workingDirectory,omitempty"` // Aggregate summary of file changes associated with this session. Servers // may populate this to give clients a quick at-a-glance view of the @@ -743,96 +714,6 @@ type ChangesSummary struct { Files *int64 `json:"files,omitempty"` } -// Full state for a single chat, loaded when a client subscribes to the chat's -// URI. -// -// The lightweight catalog representation of a chat is {@link ChatSummary}, -// carried in {@link SessionState.chats | `SessionState.chats`}. `ChatState` -// **denormalizes** every {@link ChatSummary} field directly onto itself so -// subscribers receive one flat object instead of having to merge a nested -// `summary` sub-object. Producers MUST keep the two representations -// consistent: any change to the inlined fields below SHOULD also be -// announced on the parent session via the matching -// {@link SessionChatUpdatedAction | `session/chatUpdated`} action. -type ChatState struct { - // Chat URI - Resource URI `json:"resource"` - // Chat title - Title string `json:"title"` - // Current chat status (reuses SessionStatus shape) - Status SessionStatus `json:"status"` - // Human-readable description of what the chat is currently doing - Activity *string `json:"activity,omitempty"` - // Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - ModifiedAt string `json:"modifiedAt"` - // Optional per-chat model override (defaults to the session's model) - Model *ModelSelection `json:"model,omitempty"` - // Optional per-chat agent override (defaults to the session's agent) - Agent *AgentSelection `json:"agent,omitempty"` - // How this chat came into existence - Origin *ChatOrigin `json:"origin,omitempty"` - // Optional per-chat working directory. - // - // If absent, the chat inherits - // {@link SessionSummary.workingDirectory | the session's working directory}. - // Hosts MAY override this for individual chats — for example, to give a - // subordinate chat its own git worktree so multiple chats in a session can - // make independent edits that the orchestrator later merges back. - WorkingDirectory *URI `json:"workingDirectory,omitempty"` - // Completed turns - Turns []Turn `json:"turns"` - // Currently in-progress turn - ActiveTurn *ActiveTurn `json:"activeTurn,omitempty"` - // Message to inject into the current turn at a convenient point - SteeringMessage *PendingMessage `json:"steeringMessage,omitempty"` - // Messages to send automatically as new turns after the current turn finishes - QueuedMessages []PendingMessage `json:"queuedMessages,omitempty"` - // Requests for user input that are currently blocking or informing chat progress - InputRequests []ChatInputRequest `json:"inputRequests,omitempty"` - // Additional provider-specific metadata for this chat. - Meta map[string]json.RawMessage `json:"_meta,omitempty"` -} - -// Lightweight catalog entry for a chat, carried in -// {@link SessionState.chats | `SessionState.chats`}. The full conversation -// lives in {@link ChatState}, which inlines (denormalizes) every field below. -type ChatSummary struct { - // Chat URI - Resource URI `json:"resource"` - // Chat title - Title string `json:"title"` - // Current chat status (reuses SessionStatus shape) - Status SessionStatus `json:"status"` - // Human-readable description of what the chat is currently doing - Activity *string `json:"activity,omitempty"` - // Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - ModifiedAt string `json:"modifiedAt"` - // Optional per-chat model override (defaults to the session's model) - Model *ModelSelection `json:"model,omitempty"` - // Optional per-chat agent override (defaults to the session's agent) - Agent *AgentSelection `json:"agent,omitempty"` - // How this chat came into existence - Origin *ChatOrigin `json:"origin,omitempty"` - // Optional per-chat working directory. - // - // If absent, the chat inherits - // {@link SessionSummary.workingDirectory | the session's working directory}. - // See {@link ChatState.workingDirectory} for usage notes. - WorkingDirectory *URI `json:"workingDirectory,omitempty"` -} - -// A message queued for future delivery to the agent. -// -// Steering messages are injected into the current turn mid-flight. -// Queued messages are automatically started as new turns after the -// current turn naturally finishes. -type PendingMessage struct { - // Unique identifier for this pending message - Id string `json:"id"` - // The message that will start the next turn - Message Message `json:"message"` -} - // Server-owned project metadata for a session. type ProjectInfo struct { // Project URI @@ -954,7 +835,7 @@ type Message struct { } // A choice in a select-style question. -type ChatInputOption struct { +type SessionInputOption struct { // Stable option identifier; for MCP enum values this is the enum string Id string `json:"id"` // Display label @@ -966,51 +847,51 @@ type ChatInputOption struct { } // Value captured for one answer. -type ChatInputTextAnswerValue struct { - Kind ChatInputAnswerValueKind `json:"kind"` - Value string `json:"value"` +type SessionInputTextAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` } -type ChatInputNumberAnswerValue struct { - Kind ChatInputAnswerValueKind `json:"kind"` - Value float64 `json:"value"` +type SessionInputNumberAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value float64 `json:"value"` } -type ChatInputBooleanAnswerValue struct { - Kind ChatInputAnswerValueKind `json:"kind"` - Value bool `json:"value"` +type SessionInputBooleanAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value bool `json:"value"` } -type ChatInputSelectedAnswerValue struct { - Kind ChatInputAnswerValueKind `json:"kind"` - Value string `json:"value"` +type SessionInputSelectedAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value string `json:"value"` // Free-form text entered instead of selecting an option FreeformValues []string `json:"freeformValues,omitempty"` } -type ChatInputSelectedManyAnswerValue struct { - Kind ChatInputAnswerValueKind `json:"kind"` - Value []string `json:"value"` +type SessionInputSelectedManyAnswerValue struct { + Kind SessionInputAnswerValueKind `json:"kind"` + Value []string `json:"value"` // Free-form text entered in addition to selected options FreeformValues []string `json:"freeformValues,omitempty"` } -type ChatInputAnswered struct { +type SessionInputAnswered struct { // Answer state - State ChatInputAnswerState `json:"state"` + State SessionInputAnswerState `json:"state"` // Answer value - Value ChatInputAnswerValue `json:"value"` + Value SessionInputAnswerValue `json:"value"` } -type ChatInputSkipped struct { +type SessionInputSkipped struct { // Answer state - State ChatInputAnswerState `json:"state"` + State SessionInputAnswerState `json:"state"` // Free-form reason or value captured while skipping, if any FreeformValues []string `json:"freeformValues,omitempty"` } -// Text question within a chat input request. -type ChatInputTextQuestion struct { +// Text question within a session input request. +type SessionInputTextQuestion struct { // Stable question identifier used as the key in `answers` Id string `json:"id"` // Short display title @@ -1018,8 +899,8 @@ type ChatInputTextQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind ChatInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` Format *string `json:"format,omitempty"` // Minimum string length @@ -1030,8 +911,8 @@ type ChatInputTextQuestion struct { DefaultValue *string `json:"defaultValue,omitempty"` } -// Numeric question within a chat input request. -type ChatInputNumberQuestion struct { +// Numeric question within a session input request. +type SessionInputNumberQuestion struct { // Stable question identifier used as the key in `answers` Id string `json:"id"` // Short display title @@ -1039,8 +920,8 @@ type ChatInputNumberQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind ChatInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Minimum value Min *float64 `json:"min,omitempty"` // Maximum value @@ -1049,8 +930,8 @@ type ChatInputNumberQuestion struct { DefaultValue *float64 `json:"defaultValue,omitempty"` } -// Boolean question within a chat input request. -type ChatInputBooleanQuestion struct { +// Boolean question within a session input request. +type SessionInputBooleanQuestion struct { // Stable question identifier used as the key in `answers` Id string `json:"id"` // Short display title @@ -1058,14 +939,14 @@ type ChatInputBooleanQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind ChatInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Default boolean value DefaultValue *bool `json:"defaultValue,omitempty"` } -// Single-select question within a chat input request. -type ChatInputSingleSelectQuestion struct { +// Single-select question within a session input request. +type SessionInputSingleSelectQuestion struct { // Stable question identifier used as the key in `answers` Id string `json:"id"` // Short display title @@ -1073,16 +954,16 @@ type ChatInputSingleSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind ChatInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from - Options []ChatInputOption `json:"options"` + Options []SessionInputOption `json:"options"` // Whether the user may enter text instead of selecting an option AllowFreeformInput *bool `json:"allowFreeformInput,omitempty"` } -// Multi-select question within a chat input request. -type ChatInputMultiSelectQuestion struct { +// Multi-select question within a session input request. +type SessionInputMultiSelectQuestion struct { // Stable question identifier used as the key in `answers` Id string `json:"id"` // Short display title @@ -1090,10 +971,10 @@ type ChatInputMultiSelectQuestion struct { // Prompt shown to the user Message string `json:"message"` // Whether the user must answer this question to accept the request - Required *bool `json:"required,omitempty"` - Kind ChatInputQuestionKind `json:"kind"` + Required *bool `json:"required,omitempty"` + Kind SessionInputQuestionKind `json:"kind"` // Options the user may select from - Options []ChatInputOption `json:"options"` + Options []SessionInputOption `json:"options"` // Whether the user may enter text in addition to selecting options AllowFreeformInput *bool `json:"allowFreeformInput,omitempty"` // Minimum selected item count @@ -1104,10 +985,10 @@ type ChatInputMultiSelectQuestion struct { // A live request for user input. // -// The server creates or replaces requests with `chat/inputRequested`. -// Clients sync drafts with `chat/inputAnswerChanged` and complete requests -// with `chat/inputCompleted`. -type ChatInputRequest struct { +// The server creates or replaces requests with `session/inputRequested`. +// Clients sync drafts with `session/inputAnswerChanged` and complete requests +// with `session/inputCompleted`. +type SessionInputRequest struct { // Stable request identifier Id string `json:"id"` // Display message for the request as a whole @@ -1115,9 +996,9 @@ type ChatInputRequest struct { // URL the user should review or open, for URL-style elicitations Url *URI `json:"url,omitempty"` // Ordered questions to ask the user - Questions []ChatInputQuestion `json:"questions,omitempty"` + Questions []SessionInputQuestion `json:"questions,omitempty"` // Current draft or submitted answers, keyed by question ID - Answers map[string]ChatInputAnswer `json:"answers,omitempty"` + Answers map[string]SessionInputAnswer `json:"answers,omitempty"` } // A zero-based position within a textual document. @@ -1310,7 +1191,7 @@ type MessageAnnotationsAttachment struct { type MarkdownResponsePart struct { // Discriminant Kind ResponsePartKind `json:"kind"` - // Part identifier, used by `chat/delta` to target this part for content appends + // Part identifier, used by `session/delta` to target this part for content appends Id string `json:"id"` // Markdown content Content string `json:"content"` @@ -1354,7 +1235,7 @@ type ToolCallResponsePart struct { type ReasoningResponsePart struct { // Discriminant Kind ResponsePartKind `json:"kind"` - // Part identifier, used by `chat/reasoning` to target this part for content appends + // Part identifier, used by `session/reasoning` to target this part for content appends Id string `json:"id"` // Accumulated reasoning text Content string `json:"content"` @@ -2247,7 +2128,7 @@ type ToolCallClientContributor struct { // Absent for server-side tools. // // When set, the identified client is responsible for executing the tool and - // dispatching `chat/toolCallComplete` with the result. + // dispatching `session/toolCallComplete` with the result. ClientId string `json:"clientId"` } @@ -2391,7 +2272,7 @@ type ErrorInfo struct { // A point-in-time snapshot of a subscribed resource's state, returned by // `initialize`, `reconnect`, and `subscribe`. type Snapshot struct { - // The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`) + // The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`) Resource URI `json:"resource"` // The current state of the resource State SnapshotState `json:"state"` @@ -2966,67 +2847,67 @@ func (u TerminalContentPart) MarshalJSON() ([]byte, error) { return json.Marshal(u.Value) } -// ChatInputQuestion is one question within a chat input request. -type ChatInputQuestion struct { - Value isChatInputQuestion +// SessionInputQuestion is one question within a session input request. +type SessionInputQuestion struct { + Value isSessionInputQuestion } -// isChatInputQuestion is the marker interface implemented by every -// concrete variant of ChatInputQuestion. -type isChatInputQuestion interface{ isChatInputQuestion() } +// isSessionInputQuestion is the marker interface implemented by every +// concrete variant of SessionInputQuestion. +type isSessionInputQuestion interface{ isSessionInputQuestion() } -func (*ChatInputTextQuestion) isChatInputQuestion() {} -func (*ChatInputNumberQuestion) isChatInputQuestion() {} -func (*ChatInputBooleanQuestion) isChatInputQuestion() {} -func (*ChatInputSingleSelectQuestion) isChatInputQuestion() {} -func (*ChatInputMultiSelectQuestion) isChatInputQuestion() {} +func (*SessionInputTextQuestion) isSessionInputQuestion() {} +func (*SessionInputNumberQuestion) isSessionInputQuestion() {} +func (*SessionInputBooleanQuestion) isSessionInputQuestion() {} +func (*SessionInputSingleSelectQuestion) isSessionInputQuestion() {} +func (*SessionInputMultiSelectQuestion) isSessionInputQuestion() {} -// ChatInputQuestionUnknown carries an unrecognized ChatInputQuestion variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. -type ChatInputQuestionUnknown struct { +// SessionInputQuestionUnknown carries an unrecognized SessionInputQuestion variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. +type SessionInputQuestionUnknown struct { Raw json.RawMessage } -func (*ChatInputQuestionUnknown) isChatInputQuestion() {} +func (*SessionInputQuestionUnknown) isSessionInputQuestion() {} // UnmarshalJSON decodes the variant indicated by the "kind" discriminator. -func (u *ChatInputQuestion) UnmarshalJSON(data []byte) error { +func (u *SessionInputQuestion) UnmarshalJSON(data []byte) error { disc, _, err := readDiscriminator(data, "kind") if err != nil { return err } switch disc { case "text": - var value ChatInputTextQuestion + var value SessionInputTextQuestion if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "number": - var value ChatInputNumberQuestion + var value SessionInputNumberQuestion if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "integer": - var value ChatInputNumberQuestion + var value SessionInputNumberQuestion if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "boolean": - var value ChatInputBooleanQuestion + var value SessionInputBooleanQuestion if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "single-select": - var value ChatInputSingleSelectQuestion + var value SessionInputSingleSelectQuestion if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "multi-select": - var value ChatInputMultiSelectQuestion + var value SessionInputMultiSelectQuestion if err := json.Unmarshal(data, &value); err != nil { return err } @@ -3034,14 +2915,14 @@ func (u *ChatInputQuestion) UnmarshalJSON(data []byte) error { default: raw := make(json.RawMessage, len(data)) copy(raw, data) - u.Value = &ChatInputQuestionUnknown{Raw: raw} + u.Value = &SessionInputQuestionUnknown{Raw: raw} } return nil } // MarshalJSON encodes the active variant back to JSON. -func (u ChatInputQuestion) MarshalJSON() ([]byte, error) { - if unk, ok := u.Value.(*ChatInputQuestionUnknown); ok { +func (u SessionInputQuestion) MarshalJSON() ([]byte, error) { + if unk, ok := u.Value.(*SessionInputQuestionUnknown); ok { if len(unk.Raw) == 0 { return []byte("null"), nil } @@ -3053,61 +2934,61 @@ func (u ChatInputQuestion) MarshalJSON() ([]byte, error) { return json.Marshal(u.Value) } -// ChatInputAnswerValue is the value captured for one answer. -type ChatInputAnswerValue struct { - Value isChatInputAnswerValue +// SessionInputAnswerValue is the value captured for one answer. +type SessionInputAnswerValue struct { + Value isSessionInputAnswerValue } -// isChatInputAnswerValue is the marker interface implemented by every -// concrete variant of ChatInputAnswerValue. -type isChatInputAnswerValue interface{ isChatInputAnswerValue() } +// isSessionInputAnswerValue is the marker interface implemented by every +// concrete variant of SessionInputAnswerValue. +type isSessionInputAnswerValue interface{ isSessionInputAnswerValue() } -func (*ChatInputTextAnswerValue) isChatInputAnswerValue() {} -func (*ChatInputNumberAnswerValue) isChatInputAnswerValue() {} -func (*ChatInputBooleanAnswerValue) isChatInputAnswerValue() {} -func (*ChatInputSelectedAnswerValue) isChatInputAnswerValue() {} -func (*ChatInputSelectedManyAnswerValue) isChatInputAnswerValue() {} +func (*SessionInputTextAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputNumberAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputBooleanAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputSelectedAnswerValue) isSessionInputAnswerValue() {} +func (*SessionInputSelectedManyAnswerValue) isSessionInputAnswerValue() {} -// ChatInputAnswerValueUnknown carries an unrecognized ChatInputAnswerValue variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. -type ChatInputAnswerValueUnknown struct { +// SessionInputAnswerValueUnknown carries an unrecognized SessionInputAnswerValue variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. +type SessionInputAnswerValueUnknown struct { Raw json.RawMessage } -func (*ChatInputAnswerValueUnknown) isChatInputAnswerValue() {} +func (*SessionInputAnswerValueUnknown) isSessionInputAnswerValue() {} // UnmarshalJSON decodes the variant indicated by the "kind" discriminator. -func (u *ChatInputAnswerValue) UnmarshalJSON(data []byte) error { +func (u *SessionInputAnswerValue) UnmarshalJSON(data []byte) error { disc, _, err := readDiscriminator(data, "kind") if err != nil { return err } switch disc { case "text": - var value ChatInputTextAnswerValue + var value SessionInputTextAnswerValue if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "number": - var value ChatInputNumberAnswerValue + var value SessionInputNumberAnswerValue if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "boolean": - var value ChatInputBooleanAnswerValue + var value SessionInputBooleanAnswerValue if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "selected": - var value ChatInputSelectedAnswerValue + var value SessionInputSelectedAnswerValue if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "selected-many": - var value ChatInputSelectedManyAnswerValue + var value SessionInputSelectedManyAnswerValue if err := json.Unmarshal(data, &value); err != nil { return err } @@ -3115,14 +2996,14 @@ func (u *ChatInputAnswerValue) UnmarshalJSON(data []byte) error { default: raw := make(json.RawMessage, len(data)) copy(raw, data) - u.Value = &ChatInputAnswerValueUnknown{Raw: raw} + u.Value = &SessionInputAnswerValueUnknown{Raw: raw} } return nil } // MarshalJSON encodes the active variant back to JSON. -func (u ChatInputAnswerValue) MarshalJSON() ([]byte, error) { - if unk, ok := u.Value.(*ChatInputAnswerValueUnknown); ok { +func (u SessionInputAnswerValue) MarshalJSON() ([]byte, error) { + if unk, ok := u.Value.(*SessionInputAnswerValueUnknown); ok { if len(unk.Raw) == 0 { return []byte("null"), nil } @@ -3134,46 +3015,46 @@ func (u ChatInputAnswerValue) MarshalJSON() ([]byte, error) { return json.Marshal(u.Value) } -// ChatInputAnswer is a draft, submitted, or skipped answer for one question. -type ChatInputAnswer struct { - Value isChatInputAnswer +// SessionInputAnswer is a draft, submitted, or skipped answer for one question. +type SessionInputAnswer struct { + Value isSessionInputAnswer } -// isChatInputAnswer is the marker interface implemented by every -// concrete variant of ChatInputAnswer. -type isChatInputAnswer interface{ isChatInputAnswer() } +// isSessionInputAnswer is the marker interface implemented by every +// concrete variant of SessionInputAnswer. +type isSessionInputAnswer interface{ isSessionInputAnswer() } -func (*ChatInputAnswered) isChatInputAnswer() {} -func (*ChatInputSkipped) isChatInputAnswer() {} +func (*SessionInputAnswered) isSessionInputAnswer() {} +func (*SessionInputSkipped) isSessionInputAnswer() {} -// ChatInputAnswerUnknown carries an unrecognized ChatInputAnswer variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. -type ChatInputAnswerUnknown struct { +// SessionInputAnswerUnknown carries an unrecognized SessionInputAnswer variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. +type SessionInputAnswerUnknown struct { Raw json.RawMessage } -func (*ChatInputAnswerUnknown) isChatInputAnswer() {} +func (*SessionInputAnswerUnknown) isSessionInputAnswer() {} // UnmarshalJSON decodes the variant indicated by the "state" discriminator. -func (u *ChatInputAnswer) UnmarshalJSON(data []byte) error { +func (u *SessionInputAnswer) UnmarshalJSON(data []byte) error { disc, _, err := readDiscriminator(data, "state") if err != nil { return err } switch disc { case "draft": - var value ChatInputAnswered + var value SessionInputAnswered if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "submitted": - var value ChatInputAnswered + var value SessionInputAnswered if err := json.Unmarshal(data, &value); err != nil { return err } u.Value = &value case "skipped": - var value ChatInputSkipped + var value SessionInputSkipped if err := json.Unmarshal(data, &value); err != nil { return err } @@ -3181,14 +3062,14 @@ func (u *ChatInputAnswer) UnmarshalJSON(data []byte) error { default: raw := make(json.RawMessage, len(data)) copy(raw, data) - u.Value = &ChatInputAnswerUnknown{Raw: raw} + u.Value = &SessionInputAnswerUnknown{Raw: raw} } return nil } // MarshalJSON encodes the active variant back to JSON. -func (u ChatInputAnswer) MarshalJSON() ([]byte, error) { - if unk, ok := u.Value.(*ChatInputAnswerUnknown); ok { +func (u SessionInputAnswer) MarshalJSON() ([]byte, error) { + if unk, ok := u.Value.(*SessionInputAnswerUnknown); ok { if len(unk.Raw) == 0 { return []byte("null"), nil } @@ -3732,96 +3613,14 @@ func (u ToolCallContributor) MarshalJSON() ([]byte, error) { return json.Marshal(u.Value) } -// ChatOrigin describes how a chat came into existence. -type ChatOrigin struct { - Value isChatOrigin -} - -// isChatOrigin is the marker interface for chat origin variants. -type isChatOrigin interface{ isChatOrigin() } - -type ChatUserOrigin struct { - Kind ChatOriginKind `json:"kind"` -} - -func (*ChatUserOrigin) isChatOrigin() {} - -type ChatForkOrigin struct { - Kind ChatOriginKind `json:"kind"` - Chat URI `json:"chat"` - TurnId string `json:"turnId"` -} - -func (*ChatForkOrigin) isChatOrigin() {} - -type ChatToolOrigin struct { - Kind ChatOriginKind `json:"kind"` - Chat URI `json:"chat"` - ToolCallId string `json:"toolCallId"` -} - -func (*ChatToolOrigin) isChatOrigin() {} - -type ChatOriginUnknown struct { - Raw json.RawMessage -} - -func (*ChatOriginUnknown) isChatOrigin() {} - -func (o *ChatOrigin) UnmarshalJSON(data []byte) error { - disc, _, err := readDiscriminator(data, "kind") - if err != nil { - return err - } - switch disc { - case "user": - var v ChatUserOrigin - if err := json.Unmarshal(data, &v); err != nil { - return err - } - o.Value = &v - case "fork": - var v ChatForkOrigin - if err := json.Unmarshal(data, &v); err != nil { - return err - } - o.Value = &v - case "tool": - var v ChatToolOrigin - if err := json.Unmarshal(data, &v); err != nil { - return err - } - o.Value = &v - default: - raw := make(json.RawMessage, len(data)) - copy(raw, data) - o.Value = &ChatOriginUnknown{Raw: raw} - } - return nil -} - -func (o ChatOrigin) MarshalJSON() ([]byte, error) { - if unk, ok := o.Value.(*ChatOriginUnknown); ok { - if len(unk.Raw) == 0 { - return []byte("null"), nil - } - return unk.Raw, nil - } - if o.Value == nil { - return []byte("null"), nil - } - return json.Marshal(o.Value) -} - // SnapshotState is the state payload of a snapshot — root, session, -// chat, terminal, changeset, resource-watch, or annotations state. The active +// terminal, changeset, resource-watch, or annotations state. The active // variant is chosen by which pointer field is non-nil; UnmarshalJSON probes // for required fields in the canonical order -// (session → chat → terminal → changeset → resourceWatch → annotations → root). +// (session → terminal → changeset → resourceWatch → annotations → root). type SnapshotState struct { Root *RootState `json:"-"` Session *SessionState `json:"-"` - Chat *ChatState `json:"-"` Terminal *TerminalState `json:"-"` Changeset *ChangesetState `json:"-"` ResourceWatch *ResourceWatchState `json:"-"` @@ -3833,8 +3632,6 @@ func (s SnapshotState) MarshalJSON() ([]byte, error) { switch { case s.Session != nil: return json.Marshal(s.Session) - case s.Chat != nil: - return json.Marshal(s.Chat) case s.Terminal != nil: return json.Marshal(s.Terminal) case s.Changeset != nil: @@ -3865,12 +3662,6 @@ func (s *SnapshotState) UnmarshalJSON(data []byte) error { return err } s.Session = &v - case containsAll(probe, "summary", "turns"): - var v ChatState - if err := json.Unmarshal(data, &v); err != nil { - return err - } - s.Chat = &v case containsAll(probe, "content"): var v TerminalState if err := json.Unmarshal(data, &v); err != nil { diff --git a/clients/go/examples/reducers_demo/main.go b/clients/go/examples/reducers_demo/main.go index 0bea7ef7..9cfef5e8 100644 --- a/clients/go/examples/reducers_demo/main.go +++ b/clients/go/examples/reducers_demo/main.go @@ -1,5 +1,5 @@ -// Command reducers_demo applies a handful of chat actions to an -// empty ChatState to illustrate the public reducer API. +// Command reducers_demo applies a handful of session actions to an +// empty SessionState to illustrate the public reducer API. package main import ( @@ -11,21 +11,25 @@ import ( ) func main() { - state := ahptypes.ChatState{ - Resource: "ahp-chat:/demo", - Title: "Demo", - Status: ahptypes.SessionStatusIdle, - ModifiedAt: "1970-01-01T00:00:00.001Z", + state := ahptypes.SessionState{ + Summary: ahptypes.SessionSummary{ + Resource: "ahp-session:/demo", + Provider: "demo", + Title: "Demo", + Status: ahptypes.SessionStatusIdle, + CreatedAt: 1, + }, + Lifecycle: ahptypes.SessionLifecycleReady, } actions := []ahptypes.StateAction{ - {Value: &ahptypes.ChatTurnStartedAction{ - Type: ahptypes.ActionTypeChatTurnStarted, + {Value: &ahptypes.SessionTurnStartedAction{ + Type: ahptypes.ActionTypeSessionTurnStarted, TurnId: "t1", Message: ahptypes.Message{Text: "Hello!"}, }}, - {Value: &ahptypes.ChatResponsePartAction{ - Type: ahptypes.ActionTypeChatResponsePart, + {Value: &ahptypes.SessionResponsePartAction{ + Type: ahptypes.ActionTypeSessionResponsePart, TurnId: "t1", Part: ahptypes.ResponsePart{Value: &ahptypes.MarkdownResponsePart{ Kind: ahptypes.ResponsePartKindMarkdown, @@ -33,20 +37,20 @@ func main() { Content: "Hi ", }}, }}, - {Value: &ahptypes.ChatDeltaAction{ - Type: ahptypes.ActionTypeChatDelta, + {Value: &ahptypes.SessionDeltaAction{ + Type: ahptypes.ActionTypeSessionDelta, TurnId: "t1", PartId: "p1", Content: "there!", }}, - {Value: &ahptypes.ChatTurnCompleteAction{ - Type: ahptypes.ActionTypeChatTurnComplete, + {Value: &ahptypes.SessionTurnCompleteAction{ + Type: ahptypes.ActionTypeSessionTurnComplete, TurnId: "t1", }}, } for _, a := range actions { - outcome := ahp.ApplyActionToChat(&state, a) + outcome := ahp.ApplyActionToSession(&state, a) fmt.Printf("applied %T → %v\n", a.Value, outcomeName(outcome)) } diff --git a/clients/kotlin/CHANGELOG.md b/clients/kotlin/CHANGELOG.md index 455db734..25586707 100644 --- a/clients/kotlin/CHANGELOG.md +++ b/clients/kotlin/CHANGELOG.md @@ -34,22 +34,12 @@ versions (`*-SNAPSHOT`) are explicitly rejected by the publish pipeline; bump resending its entries. Handled by the annotations reducer (no-op on unknown id). -- `ahp-chat:` channel for per-chat conversation state; `SessionState.chats[]` catalog; `SessionState.defaultChat?` input-routing hint; `ChatOrigin` provenance union; `createChat` command. -- `ChatSummary.workingDirectory` — optional per-chat working directory. Falls back to the session's `workingDirectory` when absent. -- Three discrete chat-catalog actions on the session channel — `SessionChatAddedAction` (upsert by `summary.resource`), `SessionChatRemovedAction`, and `SessionChatUpdatedAction` (partial-update payload). +### Added + - `RootState` now exposes an optional `_meta` property bag (`meta: Map?`) for implementation-defined agent-host metadata, such as a well-known `hostBuild` key carrying the host's build version/commit/date. -### Changed - -- `ChatState` is now flat — the previous embedded `summary` has been replaced with inlined `resource` / `title` / `status` / `activity` / `modifiedAt` / `model` / `agent` / `origin` / `workingDirectory` properties. `ChatSummary` remains as the standalone catalog entry on `SessionState.chats`. -- `ChatSummary.modifiedAt` and `ChatState.modifiedAt` are now ISO 8601 `String` values instead of `Long` milliseconds. - -### Removed - -- `SessionChatsChangedAction` (replaced by the three discrete chat-catalog actions above). - ## [0.3.0] — 2026-06-05 Implements AHP 0.3.0. @@ -94,13 +84,11 @@ Implements AHP 0.3.0. ### Changed -- `fetchTurns` and `completions` now target an `ahp-chat:` channel; `PROTOCOL_VERSION` bumped to `0.4.0`. - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. - Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed -- `SessionState.turns`, `SessionState.activeTurn`, `SessionState.steeringMessage`, `SessionState.queuedMessages`, `SessionState.inputRequests` (moved to `ChatState`). - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. ### Changed 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 a445fa22..c7a3b74f 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/Reducers.kt @@ -8,8 +8,143 @@ package com.microsoft.agenthostprotocol -import com.microsoft.agenthostprotocol.generated.* -import java.time.Instant +import com.microsoft.agenthostprotocol.generated.AgentSelection +import com.microsoft.agenthostprotocol.generated.ChangesetFile +import com.microsoft.agenthostprotocol.generated.ChangesetState +import com.microsoft.agenthostprotocol.generated.ChangesetStatus +import com.microsoft.agenthostprotocol.generated.ChangesetOperationStatus +import com.microsoft.agenthostprotocol.generated.ChildCustomization +import com.microsoft.agenthostprotocol.generated.ChildCustomizationAgent +import com.microsoft.agenthostprotocol.generated.ChildCustomizationHook +import com.microsoft.agenthostprotocol.generated.ChildCustomizationMcpServer +import com.microsoft.agenthostprotocol.generated.ChildCustomizationPrompt +import com.microsoft.agenthostprotocol.generated.ChildCustomizationRule +import com.microsoft.agenthostprotocol.generated.ChildCustomizationSkill +import com.microsoft.agenthostprotocol.generated.ChildCustomizationUnknown +import com.microsoft.agenthostprotocol.generated.AnnotationEntry +import com.microsoft.agenthostprotocol.generated.Annotation +import com.microsoft.agenthostprotocol.generated.AnnotationsState +import com.microsoft.agenthostprotocol.generated.ConfirmationOption +import com.microsoft.agenthostprotocol.generated.Customization +import com.microsoft.agenthostprotocol.generated.CustomizationDirectory +import com.microsoft.agenthostprotocol.generated.CustomizationMcpServer +import com.microsoft.agenthostprotocol.generated.CustomizationPlugin +import com.microsoft.agenthostprotocol.generated.CustomizationUnknown +import com.microsoft.agenthostprotocol.generated.ErrorInfo +import com.microsoft.agenthostprotocol.generated.MarkdownResponsePart +import com.microsoft.agenthostprotocol.generated.PendingMessage +import com.microsoft.agenthostprotocol.generated.PendingMessageKind +import com.microsoft.agenthostprotocol.generated.ReasoningResponsePart +import com.microsoft.agenthostprotocol.generated.ResponsePart +import com.microsoft.agenthostprotocol.generated.ResponsePartKind +import com.microsoft.agenthostprotocol.generated.ResponsePartMarkdown +import com.microsoft.agenthostprotocol.generated.ResponsePartReasoning +import com.microsoft.agenthostprotocol.generated.ResponsePartToolCall +import com.microsoft.agenthostprotocol.generated.RootState +import com.microsoft.agenthostprotocol.generated.SessionActiveClient +import com.microsoft.agenthostprotocol.generated.ResourceWatchState +import com.microsoft.agenthostprotocol.generated.SessionInputRequest +import com.microsoft.agenthostprotocol.generated.SessionLifecycle +import com.microsoft.agenthostprotocol.generated.SessionState +import com.microsoft.agenthostprotocol.generated.SessionStatus +import com.microsoft.agenthostprotocol.generated.SessionSummary +import com.microsoft.agenthostprotocol.generated.StateAction +import com.microsoft.agenthostprotocol.generated.StateActionChangesetCleared +import com.microsoft.agenthostprotocol.generated.StateActionChangesetFileRemoved +import com.microsoft.agenthostprotocol.generated.StateActionChangesetFileSet +import com.microsoft.agenthostprotocol.generated.StateActionChangesetOperationsChanged +import com.microsoft.agenthostprotocol.generated.StateActionChangesetOperationStatusChanged +import com.microsoft.agenthostprotocol.generated.StateActionChangesetStatusChanged +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsEntryRemoved +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsEntrySet +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsRemoved +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsSet +import com.microsoft.agenthostprotocol.generated.StateActionAnnotationsUpdated +import com.microsoft.agenthostprotocol.generated.StateActionRootActiveSessionsChanged +import com.microsoft.agenthostprotocol.generated.StateActionRootAgentsChanged +import com.microsoft.agenthostprotocol.generated.StateActionRootConfigChanged +import com.microsoft.agenthostprotocol.generated.StateActionRootTerminalsChanged +import com.microsoft.agenthostprotocol.generated.StateActionResourceWatchChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionActiveClientChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionActiveClientToolsChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionActivityChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionAgentChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionChangesetsChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionConfigChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionCreationFailed +import com.microsoft.agenthostprotocol.generated.StateActionSessionCustomizationRemoved +import com.microsoft.agenthostprotocol.generated.StateActionSessionCustomizationToggled +import com.microsoft.agenthostprotocol.generated.StateActionSessionCustomizationUpdated +import com.microsoft.agenthostprotocol.generated.StateActionSessionCustomizationsChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionDelta +import com.microsoft.agenthostprotocol.generated.StateActionSessionError +import com.microsoft.agenthostprotocol.generated.StateActionSessionInputAnswerChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionInputCompleted +import com.microsoft.agenthostprotocol.generated.StateActionSessionInputRequested +import com.microsoft.agenthostprotocol.generated.StateActionSessionIsArchivedChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionIsReadChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionMcpServerStateChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionMetaChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionModelChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionPendingMessageRemoved +import com.microsoft.agenthostprotocol.generated.StateActionSessionPendingMessageSet +import com.microsoft.agenthostprotocol.generated.StateActionSessionQueuedMessagesReordered +import com.microsoft.agenthostprotocol.generated.StateActionSessionReady +import com.microsoft.agenthostprotocol.generated.StateActionSessionReasoning +import com.microsoft.agenthostprotocol.generated.StateActionSessionResponsePart +import com.microsoft.agenthostprotocol.generated.StateActionSessionServerToolsChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionTitleChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionToolCallComplete +import com.microsoft.agenthostprotocol.generated.StateActionSessionToolCallConfirmed +import com.microsoft.agenthostprotocol.generated.StateActionSessionToolCallContentChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionToolCallDelta +import com.microsoft.agenthostprotocol.generated.StateActionSessionToolCallReady +import com.microsoft.agenthostprotocol.generated.StateActionSessionToolCallResultConfirmed +import com.microsoft.agenthostprotocol.generated.StateActionSessionToolCallStart +import com.microsoft.agenthostprotocol.generated.StateActionSessionTruncated +import com.microsoft.agenthostprotocol.generated.StateActionSessionTurnCancelled +import com.microsoft.agenthostprotocol.generated.StateActionSessionTurnComplete +import com.microsoft.agenthostprotocol.generated.StateActionSessionTurnStarted +import com.microsoft.agenthostprotocol.generated.StateActionSessionUsage +import com.microsoft.agenthostprotocol.generated.StateActionTerminalClaimed +import com.microsoft.agenthostprotocol.generated.StateActionTerminalCleared +import com.microsoft.agenthostprotocol.generated.StateActionTerminalCommandDetectionAvailable +import com.microsoft.agenthostprotocol.generated.StateActionTerminalCommandExecuted +import com.microsoft.agenthostprotocol.generated.StateActionTerminalCommandFinished +import com.microsoft.agenthostprotocol.generated.StateActionTerminalCwdChanged +import com.microsoft.agenthostprotocol.generated.StateActionTerminalData +import com.microsoft.agenthostprotocol.generated.StateActionTerminalExited +import com.microsoft.agenthostprotocol.generated.StateActionTerminalInput +import com.microsoft.agenthostprotocol.generated.StateActionTerminalResized +import com.microsoft.agenthostprotocol.generated.StateActionTerminalTitleChanged +import com.microsoft.agenthostprotocol.generated.TerminalCommandPart +import com.microsoft.agenthostprotocol.generated.TerminalContentPart +import com.microsoft.agenthostprotocol.generated.TerminalContentPartCommand +import com.microsoft.agenthostprotocol.generated.TerminalContentPartUnclassified +import com.microsoft.agenthostprotocol.generated.TerminalState +import com.microsoft.agenthostprotocol.generated.TerminalUnclassifiedPart +import com.microsoft.agenthostprotocol.generated.ToolCallCancellationReason +import com.microsoft.agenthostprotocol.generated.ToolCallCancelledState +import com.microsoft.agenthostprotocol.generated.ToolCallCompletedState +import com.microsoft.agenthostprotocol.generated.ToolCallConfirmationReason +import com.microsoft.agenthostprotocol.generated.ToolCallContributor +import com.microsoft.agenthostprotocol.generated.ToolCallPendingConfirmationState +import com.microsoft.agenthostprotocol.generated.ToolCallPendingResultConfirmationState +import com.microsoft.agenthostprotocol.generated.ToolCallResponsePart +import com.microsoft.agenthostprotocol.generated.ToolCallRunningState +import com.microsoft.agenthostprotocol.generated.ToolCallState +import com.microsoft.agenthostprotocol.generated.ToolCallStateCancelled +import com.microsoft.agenthostprotocol.generated.ToolCallStateCompleted +import com.microsoft.agenthostprotocol.generated.ToolCallStatePendingConfirmation +import com.microsoft.agenthostprotocol.generated.ToolCallStatePendingResultConfirmation +import com.microsoft.agenthostprotocol.generated.ToolCallStateRunning +import com.microsoft.agenthostprotocol.generated.ToolCallStateStreaming +import com.microsoft.agenthostprotocol.generated.ToolCallStateUnknown +import com.microsoft.agenthostprotocol.generated.ToolCallStatus +import com.microsoft.agenthostprotocol.generated.ToolCallStreamingState +import com.microsoft.agenthostprotocol.generated.Turn +import com.microsoft.agenthostprotocol.generated.ActiveTurn +import com.microsoft.agenthostprotocol.generated.TurnState import kotlinx.serialization.json.JsonElement // ─── Reducer Interface ────────────────────────────────────────────────────── @@ -40,12 +175,6 @@ public object SessionReducer : Reducer { sessionReducer(state, action) } -/** Pure chat reducer as a [Reducer] instance. Delegates to [chatReducer]. */ -public object ChatReducer : Reducer { - override fun reduce(state: ChatState, action: StateAction): ChatState = - chatReducer(state, action) -} - /** Pure terminal reducer as a [Reducer] instance. Delegates to [terminalReducer]. */ public object TerminalReducer : Reducer { override fun reduce(state: TerminalState, action: StateAction): TerminalState = @@ -81,7 +210,6 @@ public object ResourceWatchReducer : Reducer { public var currentTimestampProvider: () -> Long = { System.currentTimeMillis() } private fun now(): Long = currentTimestampProvider() -private fun nowIsoString(): String = Instant.ofEpochMilli(currentTimestampProvider()).toString() // ─── Status Bitset Helpers ────────────────────────────────────────────────── @@ -97,7 +225,7 @@ private fun withStatusFlag(status: SessionStatus, flag: SessionStatus, set: Bool } /** Derives the summary status from live session work, preserving orthogonal flags. */ -private fun chatSummaryStatus(state: ChatState, terminalStatus: SessionStatus? = null): SessionStatus { +private fun summaryStatus(state: SessionState, terminalStatus: SessionStatus? = null): SessionStatus { val activity: SessionStatus = when { terminalStatus != null -> terminalStatus (state.inputRequests?.size ?: 0) > 0 || hasPendingToolCallConfirmation(state) -> @@ -105,25 +233,25 @@ private fun chatSummaryStatus(state: ChatState, terminalStatus: SessionStatus? = state.activeTurn != null -> SessionStatus.IN_PROGRESS else -> SessionStatus.IDLE } - val preserved = state.status.rawValue and STATUS_ACTIVITY_MASK.inv() + val preserved = state.summary.status.rawValue and STATUS_ACTIVITY_MASK.inv() return SessionStatus(preserved or activity.rawValue) } /** - * Returns a state with chat [ChatState.status] recomputed. Use after reducers that - * change data feeding into [chatSummaryStatus] (e.g. tool call lifecycle + * Returns a state with `summary.status` recomputed. Use after reducers that + * change data feeding into [summaryStatus] (e.g. tool call lifecycle * transitions that may enter or leave a pending-confirmation state). */ -private fun refreshChatSummaryStatus(state: ChatState): ChatState { - val status = chatSummaryStatus(state) - if (status.rawValue == state.status.rawValue) { +private fun refreshSummaryStatus(state: SessionState): SessionState { + val status = summaryStatus(state) + if (status.rawValue == state.summary.status.rawValue) { return state } - return state.copy(status = status) + return state.copy(summary = state.summary.copy(status = status)) } /** Returns `true` if the active turn has any tool call awaiting user confirmation. */ -private fun hasPendingToolCallConfirmation(state: ChatState): Boolean { +private fun hasPendingToolCallConfirmation(state: SessionState): Boolean { val active = state.activeTurn ?: return false return active.responseParts.any { part -> part is ResponsePartToolCall && @@ -227,11 +355,11 @@ private fun childCustomizationId(c: ChildCustomization): String? = when (c) { * active turn or tool call doesn't match. */ private fun updateToolCallInParts( - state: ChatState, + state: SessionState, turnId: String, toolCallId: String, updater: (ToolCallState) -> ToolCallState, -): ChatState { +): SessionState { val activeTurn = state.activeTurn ?: return state if (activeTurn.id != turnId) return state @@ -259,11 +387,11 @@ private fun updateToolCallInParts( * matches on `toolCall.toolCallId`. */ private fun updateResponsePart( - state: ChatState, + state: SessionState, turnId: String, partId: String, updater: (ResponsePart) -> ResponsePart, -): ChatState { +): SessionState { val activeTurn = state.activeTurn ?: return state if (activeTurn.id != turnId) return state @@ -296,12 +424,12 @@ private fun updateResponsePart( * stripped from those tool call parts in the process. */ private fun endTurn( - state: ChatState, + state: SessionState, turnId: String, turnState: TurnState, terminalStatus: SessionStatus? = null, error: ErrorInfo? = null, -): ChatState { +): SessionState { val active = state.activeTurn ?: return state if (active.id != turnId) return state @@ -364,15 +492,17 @@ private fun endTurn( turns = state.turns + turn, activeTurn = null, inputRequests = null, - modifiedAt = nowIsoString(), + summary = state.summary.copy(modifiedAt = now()), + ) + return withoutTurn.copy( + summary = withoutTurn.summary.copy(status = summaryStatus(withoutTurn, terminalStatus)), ) - return withoutTurn.copy(status = chatSummaryStatus(withoutTurn, terminalStatus)) } -private fun upsertInputRequest(state: ChatState, request: ChatInputRequest): ChatState { +private fun upsertInputRequest(state: SessionState, request: SessionInputRequest): SessionState { val existing = state.inputRequests ?: emptyList() val idx = existing.indexOfFirst { it.id == request.id } - val updated: List = if (idx >= 0) { + val updated: List = if (idx >= 0) { val priorAnswers = existing[idx].answers existing.toMutableList().also { it[idx] = request.copy(answers = request.answers ?: priorAnswers) } } else { @@ -380,8 +510,10 @@ private fun upsertInputRequest(state: ChatState, request: ChatInputRequest): Cha } val next = state.copy(inputRequests = updated) return next.copy( - status = withStatusFlag(chatSummaryStatus(next), SessionStatus.IS_READ, false), - modifiedAt = nowIsoString(), + summary = next.summary.copy( + status = withStatusFlag(summaryStatus(next), SessionStatus.IS_READ, false), + modifiedAt = now(), + ), ) } @@ -427,218 +559,25 @@ public fun rootReducer(state: RootState, action: StateAction): RootState = when * no-ops that return [state] unchanged. */ public fun sessionReducer(state: SessionState, action: StateAction): SessionState = when (action) { - is StateActionSessionReady -> state.copy(lifecycle = SessionLifecycle.READY) - - is StateActionSessionCreationFailed -> state.copy( - lifecycle = SessionLifecycle.CREATION_FAILED, - creationError = action.value.error, - ) - is StateActionSessionChatAdded -> { - val summary = action.value.summary - val idx = state.chats.indexOfFirst { it.resource == summary.resource } - if (idx < 0) { - state.copy(chats = state.chats + summary) - } else { - val updated = state.chats.toMutableList() - updated[idx] = summary - state.copy(chats = updated) - } - } + // ── Lifecycle ────────────────────────────────────────────────────────── - is StateActionSessionChatRemoved -> { - val chat = action.value.chat - val idx = state.chats.indexOfFirst { it.resource == chat } - if (idx < 0) { - state - } else { - val updated = state.chats.toMutableList() - updated.removeAt(idx) - state.copy( - chats = updated, - defaultChat = if (state.defaultChat == chat) null else state.defaultChat, - ) - } + is StateActionSessionReady -> { + // Lifecycle-only transition (Creating → Ready). Must not touch + // `summary.status`: for provisional sessions the first turn can + // start before materialisation completes, so an `activeTurn` may + // already be set. The TS reference impl notes this in detail. + state.copy(lifecycle = SessionLifecycle.READY) } - is StateActionSessionChatUpdated -> { - val a = action.value - val idx = state.chats.indexOfFirst { it.resource == a.chat } - if (idx < 0) { - state - } else { - val prior = state.chats[idx] - val c = a.changes - val updatedSummary = prior.copy( - title = c.title ?: prior.title, - status = c.status ?: prior.status, - activity = c.activity ?: prior.activity, - modifiedAt = c.modifiedAt ?: prior.modifiedAt, - model = c.model ?: prior.model, - agent = c.agent ?: prior.agent, - origin = c.origin ?: prior.origin, - workingDirectory = c.workingDirectory ?: prior.workingDirectory, - ) - val updated = state.chats.toMutableList() - updated[idx] = updatedSummary - state.copy(chats = updated) - } - } - - is StateActionSessionDefaultChatChanged -> state.copy(defaultChat = action.value.defaultChat) - - is StateActionSessionTitleChanged -> state.copy( - summary = state.summary.copy(title = action.value.title, modifiedAt = now()), - ) - - is StateActionSessionModelChanged -> state.copy( - summary = state.summary.copy(model = action.value.model, modifiedAt = now()), - ) - - is StateActionSessionAgentChanged -> state.copy( - summary = state.summary.copy(agent = action.value.agent, modifiedAt = now()), - ) - - is StateActionSessionIsReadChanged -> state.copy( - summary = state.summary.copy( - status = withStatusFlag(state.summary.status, SessionStatus.IS_READ, action.value.isRead), - ), - ) - - is StateActionSessionIsArchivedChanged -> state.copy( - summary = state.summary.copy( - status = withStatusFlag(state.summary.status, SessionStatus.IS_ARCHIVED, action.value.isArchived), - ), - ) - - is StateActionSessionActivityChanged -> state.copy( - summary = state.summary.copy(activity = action.value.activity), + is StateActionSessionCreationFailed -> state.copy( + lifecycle = SessionLifecycle.CREATION_FAILED, + creationError = action.value.error, ) - is StateActionSessionChangesetsChanged -> state.copy(changesets = action.value.changesets) - - is StateActionSessionConfigChanged -> { - val a = action.value - val config = state.config - if (config == null) state else { - val newValues = if (a.replace == true) a.config else config.values + a.config - state.copy(config = config.copy(values = newValues), summary = state.summary.copy(modifiedAt = now())) - } - } - - is StateActionSessionMetaChanged -> state.copy(meta = action.value.meta) - - is StateActionSessionServerToolsChanged -> state.copy(serverTools = action.value.tools) - - is StateActionSessionActiveClientChanged -> state.copy(activeClient = action.value.activeClient) - - is StateActionSessionActiveClientToolsChanged -> { - val client = state.activeClient - if (client == null) state else state.copy(activeClient = client.copy(tools = action.value.tools)) - } - - is StateActionSessionCustomizationsChanged -> state.copy(customizations = action.value.customizations) - - is StateActionSessionCustomizationToggled -> { - val a = action.value - val list = state.customizations - if (list == null) state else { - val idx = list.indexOfFirst { customizationId(it) == a.id } - if (idx < 0) state else { - val updated = list.toMutableList() - updated[idx] = withCustomizationEnabled(updated[idx], a.enabled) - state.copy(customizations = updated) - } - } - } - - is StateActionSessionCustomizationUpdated -> { - val a = action.value - val targetId = customizationId(a.customization) - if (targetId == null) state else { - val list = state.customizations ?: emptyList() - val idx = list.indexOfFirst { customizationId(it) == targetId } - if (idx < 0) state.copy(customizations = list + a.customization) else { - val updated = list.toMutableList() - updated[idx] = a.customization - state.copy(customizations = updated) - } - } - } - - is StateActionSessionCustomizationRemoved -> { - val a = action.value - val list = state.customizations - if (list == null) state else { - val topIdx = list.indexOfFirst { customizationId(it) == a.id } - if (topIdx >= 0) { - val updated = list.toMutableList() - updated.removeAt(topIdx) - state.copy(customizations = updated) - } else { - var changed = false - val updated = list.map { container -> - val children = customizationChildren(container) - if (children == null) container else { - val childIdx = children.indexOfFirst { childCustomizationId(it) == a.id } - if (childIdx < 0) container else { - changed = true - val newChildren = children.toMutableList() - newChildren.removeAt(childIdx) - withCustomizationChildren(container, newChildren) - } - } - } - if (!changed) state else state.copy(customizations = updated) - } - } - } - - is StateActionSessionMcpServerStateChanged -> { - val a = action.value - val list = state.customizations - if (list == null) state else { - val topIdx = list.indexOfFirst { customizationId(it) == a.id } - if (topIdx >= 0) { - val entry = list[topIdx] - if (entry !is CustomizationMcpServer) state else { - val updated = list.toMutableList() - updated[topIdx] = CustomizationMcpServer(entry.value.copy(state = a.state, channel = a.channel)) - state.copy(customizations = updated) - } - } else { - var changed = false - val updated = list.map { container -> - val children = customizationChildren(container) - if (children == null) container else { - val childIdx = children.indexOfFirst { childCustomizationId(it) == a.id } - if (childIdx < 0) container else { - val child = children[childIdx] - if (child !is ChildCustomizationMcpServer) container else { - changed = true - val newChildren = children.toMutableList() - newChildren[childIdx] = ChildCustomizationMcpServer(child.value.copy(state = a.state, channel = a.channel)) - withCustomizationChildren(container, newChildren) - } - } - } - } - if (!changed) state else state.copy(customizations = updated) - } - } - } - - else -> state -} - -// ─── Chat Reducer ─────────────────────────────────────────────────────────── - -/** Pure reducer for [ChatState]. Handles all chat-channel action variants. */ -public fun chatReducer(state: ChatState, action: StateAction): ChatState = when (action) { - // ── Turn Lifecycle ──────────────────────────────────────────────────── - is StateActionChatTurnStarted -> { + is StateActionSessionTurnStarted -> { val a = action.value val withTurn = state.copy( activeTurn = ActiveTurn( @@ -649,8 +588,10 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when ), ) val withStatus = withTurn.copy( - status = withStatusFlag(chatSummaryStatus(withTurn), SessionStatus.IS_READ, false), - modifiedAt = nowIsoString(), + summary = withTurn.summary.copy( + status = withStatusFlag(summaryStatus(withTurn), SessionStatus.IS_READ, false), + modifiedAt = now(), + ), ) if (a.queuedMessageId == null) { withStatus @@ -668,7 +609,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } - is StateActionChatDelta -> { + is StateActionSessionDelta -> { val a = action.value updateResponsePart(state, a.turnId, a.partId) { part -> if (part is ResponsePartMarkdown) { @@ -679,7 +620,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } - is StateActionChatResponsePart -> { + is StateActionSessionResponsePart -> { val a = action.value val activeTurn = state.activeTurn if (activeTurn == null || activeTurn.id != a.turnId) { @@ -691,18 +632,18 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } - is StateActionChatTurnComplete -> + is StateActionSessionTurnComplete -> endTurn(state, action.value.turnId, TurnState.COMPLETE) - is StateActionChatTurnCancelled -> + is StateActionSessionTurnCancelled -> endTurn(state, action.value.turnId, TurnState.CANCELLED) - is StateActionChatError -> + is StateActionSessionError -> endTurn(state, action.value.turnId, TurnState.ERROR, SessionStatus.ERROR, action.value.error) // ── Tool Call State Machine ─────────────────────────────────────────── - is StateActionChatToolCallStart -> { + is StateActionSessionToolCallStart -> { val a = action.value val activeTurn = state.activeTurn if (activeTurn == null || activeTurn.id != a.turnId) { @@ -727,7 +668,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } - is StateActionChatToolCallDelta -> { + is StateActionSessionToolCallDelta -> { val a = action.value updateToolCallInParts(state, a.turnId, a.toolCallId) { tc -> if (tc !is ToolCallStateStreaming) tc else { @@ -742,9 +683,9 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } - is StateActionChatToolCallReady -> { + is StateActionSessionToolCallReady -> { val a = action.value - refreshChatSummaryStatus( + refreshSummaryStatus( updateToolCallInParts(state, a.turnId, a.toolCallId) { tc -> if (tc !is ToolCallStateStreaming && tc !is ToolCallStateRunning) { tc @@ -787,9 +728,9 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when ) } - is StateActionChatToolCallConfirmed -> { + is StateActionSessionToolCallConfirmed -> { val a = action.value - refreshChatSummaryStatus( + refreshSummaryStatus( updateToolCallInParts(state, a.turnId, a.toolCallId) { tc -> if (tc !is ToolCallStatePendingConfirmation) tc else { val base = toolCallBase(tc).withMeta(a.meta) @@ -833,10 +774,10 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when ) } - is StateActionChatToolCallComplete -> { + is StateActionSessionToolCallComplete -> { val a = action.value val result = a.result - refreshChatSummaryStatus( + refreshSummaryStatus( updateToolCallInParts(state, a.turnId, a.toolCallId) { tc -> val (invocationMessage, toolInput, confirmed, selectedOption) = when (tc) { is ToolCallStateRunning -> CompleteCtx( @@ -899,9 +840,9 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when ) } - is StateActionChatToolCallResultConfirmed -> { + is StateActionSessionToolCallResultConfirmed -> { val a = action.value - refreshChatSummaryStatus( + refreshSummaryStatus( updateToolCallInParts(state, a.turnId, a.toolCallId) { tc -> if (tc !is ToolCallStatePendingResultConfirmation) tc else { val base = toolCallBase(tc).withMeta(a.meta) @@ -946,7 +887,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when ) } - is StateActionChatToolCallContentChanged -> { + is StateActionSessionToolCallContentChanged -> { val a = action.value updateToolCallInParts(state, a.turnId, a.toolCallId) { tc -> if (tc !is ToolCallStateRunning) tc else { @@ -956,7 +897,12 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } // ── Metadata ────────────────────────────────────────────────────────── - is StateActionChatUsage -> { + + is StateActionSessionTitleChanged -> state.copy( + summary = state.summary.copy(title = action.value.title, modifiedAt = now()), + ) + + is StateActionSessionUsage -> { val a = action.value val activeTurn = state.activeTurn if (activeTurn == null || activeTurn.id != a.turnId) { @@ -966,7 +912,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } - is StateActionChatReasoning -> { + is StateActionSessionReasoning -> { val a = action.value updateResponsePart(state, a.turnId, a.partId) { part -> if (part is ResponsePartReasoning) { @@ -976,32 +922,220 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } } + + is StateActionSessionModelChanged -> state.copy( + summary = state.summary.copy(model = action.value.model, modifiedAt = now()), + ) + + is StateActionSessionAgentChanged -> state.copy( + summary = state.summary.copy(agent = action.value.agent, modifiedAt = now()), + ) + + is StateActionSessionIsReadChanged -> state.copy( + summary = state.summary.copy( + status = withStatusFlag(state.summary.status, SessionStatus.IS_READ, action.value.isRead), + ), + ) + + is StateActionSessionIsArchivedChanged -> state.copy( + summary = state.summary.copy( + status = withStatusFlag(state.summary.status, SessionStatus.IS_ARCHIVED, action.value.isArchived), + ), + ) + + is StateActionSessionActivityChanged -> state.copy( + summary = state.summary.copy(activity = action.value.activity), + ) + + is StateActionSessionChangesetsChanged -> state.copy( + changesets = action.value.changesets, + ) + + is StateActionSessionConfigChanged -> { + val a = action.value + val config = state.config + if (config == null) { + state + } else { + val newValues = if (a.replace == true) a.config else config.values + a.config + state.copy( + config = config.copy(values = newValues), + summary = state.summary.copy(modifiedAt = now()), + ) + } + } + + is StateActionSessionMetaChanged -> state.copy(meta = action.value.meta) + + is StateActionSessionServerToolsChanged -> state.copy(serverTools = action.value.tools) + + is StateActionSessionActiveClientChanged -> state.copy(activeClient = action.value.activeClient) + + is StateActionSessionActiveClientToolsChanged -> { + val client = state.activeClient + if (client == null) { + state + } else { + state.copy(activeClient = client.copy(tools = action.value.tools)) + } + } + + // ── Customizations ──────────────────────────────────────────────────── + + is StateActionSessionCustomizationsChanged -> + state.copy(customizations = action.value.customizations) + + is StateActionSessionCustomizationToggled -> { + val a = action.value + val list = state.customizations + if (list == null) { + state + } else { + val idx = list.indexOfFirst { customizationId(it) == a.id } + if (idx < 0) { + state + } else { + val updated = list.toMutableList() + updated[idx] = withCustomizationEnabled(updated[idx], a.enabled) + state.copy(customizations = updated) + } + } + } + + is StateActionSessionCustomizationUpdated -> { + val a = action.value + // Match Rust: an unknown customization has no id, so we can't locate or + // insert it sensibly — NoOp the update entirely. + val targetId = customizationId(a.customization) + if (targetId == null) { + state + } else { + val list = state.customizations ?: emptyList() + val idx = list.indexOfFirst { customizationId(it) == targetId } + if (idx < 0) { + state.copy(customizations = list + a.customization) + } else { + val updated = list.toMutableList() + updated[idx] = a.customization + state.copy(customizations = updated) + } + } + } + + is StateActionSessionCustomizationRemoved -> { + val a = action.value + val list = state.customizations + if (list == null) { + state + } else { + val topIdx = list.indexOfFirst { customizationId(it) == a.id } + if (topIdx >= 0) { + val updated = list.toMutableList() + updated.removeAt(topIdx) + state.copy(customizations = updated) + } else { + var changed = false + val updated = list.map { container -> + val children = customizationChildren(container) + if (children == null) { + container + } else { + val childIdx = children.indexOfFirst { childCustomizationId(it) == a.id } + if (childIdx < 0) { + container + } else { + changed = true + val newChildren = children.toMutableList() + newChildren.removeAt(childIdx) + withCustomizationChildren(container, newChildren) + } + } + } + if (!changed) state else state.copy(customizations = updated) + } + } + } + + is StateActionSessionMcpServerStateChanged -> { + // Full-replacement of an MCP server customization's `state` + `channel`, + // located by id. Mirrors the canonical TS reducer (and the Go/Rust/Swift + // ports): a top-level McpServer entry is matched first (hosts MAY surface + // MCP servers directly at the top level); otherwise the search descends + // into container children. A no-op when no customization carries the id, + // or when the matched id belongs to a non-MCP customization type. + val a = action.value + val list = state.customizations + if (list == null) { + state + } else { + val topIdx = list.indexOfFirst { customizationId(it) == a.id } + if (topIdx >= 0) { + val entry = list[topIdx] + if (entry !is CustomizationMcpServer) { + state + } else { + val updated = list.toMutableList() + updated[topIdx] = CustomizationMcpServer( + entry.value.copy(state = a.state, channel = a.channel), + ) + state.copy(customizations = updated) + } + } else { + var changed = false + val updated = list.map { container -> + val children = customizationChildren(container) + if (children == null) { + container + } else { + val childIdx = children.indexOfFirst { childCustomizationId(it) == a.id } + if (childIdx < 0) { + container + } else { + val child = children[childIdx] + if (child !is ChildCustomizationMcpServer) { + container + } else { + changed = true + val newChildren = children.toMutableList() + newChildren[childIdx] = ChildCustomizationMcpServer( + child.value.copy(state = a.state, channel = a.channel), + ) + withCustomizationChildren(container, newChildren) + } + } + } + } + if (!changed) state else state.copy(customizations = updated) + } + } + } + // ── Truncation ──────────────────────────────────────────────────────── - is StateActionChatTruncated -> { + is StateActionSessionTruncated -> { val a = action.value val turns = if (a.turnId == null) { emptyList() } else { val idx = state.turns.indexOfFirst { it.id == a.turnId } - if (idx < 0) return@chatReducer state + if (idx < 0) return@sessionReducer state state.turns.subList(0, idx + 1).toList() } val next = state.copy( turns = turns, activeTurn = null, inputRequests = null, - modifiedAt = nowIsoString(), + summary = state.summary.copy(modifiedAt = now()), ) - next.copy(status = chatSummaryStatus(next)) + next.copy(summary = next.summary.copy(status = summaryStatus(next))) } // ── Session Input Requests ──────────────────────────────────────────── - is StateActionChatInputRequested -> + is StateActionSessionInputRequested -> upsertInputRequest(state, action.value.request) - is StateActionChatInputAnswerChanged -> { + is StateActionSessionInputAnswerChanged -> { val a = action.value val existing = state.inputRequests val idx = existing?.indexOfFirst { it.id == a.requestId } ?: -1 @@ -1019,12 +1153,12 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when val updated = existing.toMutableList().also { it[idx] = newRequest } state.copy( inputRequests = updated, - modifiedAt = nowIsoString(), + summary = state.summary.copy(modifiedAt = now()), ) } } - is StateActionChatInputCompleted -> { + is StateActionSessionInputCompleted -> { val a = action.value val existing = state.inputRequests if (existing == null || existing.none { it.id == a.requestId }) { @@ -1033,15 +1167,17 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when val remaining = existing.filter { it.id != a.requestId } val next = state.copy(inputRequests = remaining.ifEmpty { null }) next.copy( - status = chatSummaryStatus(next), - modifiedAt = nowIsoString(), + summary = next.summary.copy( + status = summaryStatus(next), + modifiedAt = now(), + ), ) } } // ── Pending Messages ────────────────────────────────────────────────── - is StateActionChatPendingMessageSet -> { + is StateActionSessionPendingMessageSet -> { val a = action.value val entry = PendingMessage(id = a.id, message = a.message) if (a.kind == PendingMessageKind.STEERING) { @@ -1058,7 +1194,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } - is StateActionChatPendingMessageRemoved -> { + is StateActionSessionPendingMessageRemoved -> { val a = action.value if (a.kind == PendingMessageKind.STEERING) { val steering = state.steeringMessage @@ -1068,7 +1204,7 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when state.copy(steeringMessage = null) } } else { - val existing = state.queuedMessages ?: return@chatReducer state + val existing = state.queuedMessages ?: return@sessionReducer state val filtered = existing.filter { it.id != a.id } if (filtered.size == existing.size) { state @@ -1078,9 +1214,9 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } } - is StateActionChatQueuedMessagesReordered -> { + is StateActionSessionQueuedMessagesReordered -> { val a = action.value - val existing = state.queuedMessages ?: return@chatReducer state + val existing = state.queuedMessages ?: return@sessionReducer state val byId = existing.associateBy { it.id } val ordered = LinkedHashSet() val reordered = mutableListOf() @@ -1099,13 +1235,12 @@ public fun chatReducer(state: ChatState, action: StateAction): ChatState = when } else -> state - } /** - * Locally scoped helper for tool-call completion to avoid Pair/Triple noise - * when carrying the four context fields from the prior tool call state into - * the new one. + * Locally scoped helper for [StateActionSessionToolCallComplete] to avoid + * Pair/Triple noise when carrying the four context fields from the prior + * tool call state into the new one. */ private data class CompleteCtx( val invocationMessage: com.microsoft.agenthostprotocol.generated.StringOrMarkdown, @@ -1114,7 +1249,6 @@ private data class CompleteCtx( val selectedOption: ConfirmationOption?, ) - // ─── Terminal Reducer ─────────────────────────────────────────────────────── /** @@ -1321,7 +1455,7 @@ public fun annotationsReducer(state: AnnotationsState, action: StateAction): Ann if (idx < 0) { state } else { - val next = state.annotations.toMutableList().also { it.removeAt(idx) } + val next: List = state.annotations.toMutableList().also { it.removeAt(idx) } state.copy(annotations = next) } } 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 b20eac2f..3205f25c 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 @@ -34,46 +34,38 @@ enum class ActionType { 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/turnComplete") - CHAT_TURN_COMPLETE, - @SerialName("chat/turnCancelled") - CHAT_TURN_CANCELLED, - @SerialName("chat/error") - CHAT_ERROR, + @SerialName("session/turnStarted") + SESSION_TURN_STARTED, + @SerialName("session/delta") + SESSION_DELTA, + @SerialName("session/responsePart") + SESSION_RESPONSE_PART, + @SerialName("session/toolCallStart") + SESSION_TOOL_CALL_START, + @SerialName("session/toolCallDelta") + SESSION_TOOL_CALL_DELTA, + @SerialName("session/toolCallReady") + SESSION_TOOL_CALL_READY, + @SerialName("session/toolCallConfirmed") + SESSION_TOOL_CALL_CONFIRMED, + @SerialName("session/toolCallComplete") + SESSION_TOOL_CALL_COMPLETE, + @SerialName("session/toolCallResultConfirmed") + SESSION_TOOL_CALL_RESULT_CONFIRMED, + @SerialName("session/toolCallContentChanged") + SESSION_TOOL_CALL_CONTENT_CHANGED, + @SerialName("session/turnComplete") + SESSION_TURN_COMPLETE, + @SerialName("session/turnCancelled") + SESSION_TURN_CANCELLED, + @SerialName("session/error") + SESSION_ERROR, @SerialName("session/titleChanged") SESSION_TITLE_CHANGED, - @SerialName("chat/usage") - CHAT_USAGE, - @SerialName("chat/reasoning") - CHAT_REASONING, + @SerialName("session/usage") + SESSION_USAGE, + @SerialName("session/reasoning") + SESSION_REASONING, @SerialName("session/modelChanged") SESSION_MODEL_CHANGED, @SerialName("session/agentChanged") @@ -84,18 +76,18 @@ enum class ActionType { SESSION_ACTIVE_CLIENT_CHANGED, @SerialName("session/activeClientToolsChanged") SESSION_ACTIVE_CLIENT_TOOLS_CHANGED, - @SerialName("chat/pendingMessageSet") - CHAT_PENDING_MESSAGE_SET, - @SerialName("chat/pendingMessageRemoved") - CHAT_PENDING_MESSAGE_REMOVED, - @SerialName("chat/queuedMessagesReordered") - CHAT_QUEUED_MESSAGES_REORDERED, - @SerialName("chat/inputRequested") - CHAT_INPUT_REQUESTED, - @SerialName("chat/inputAnswerChanged") - CHAT_INPUT_ANSWER_CHANGED, - @SerialName("chat/inputCompleted") - CHAT_INPUT_COMPLETED, + @SerialName("session/pendingMessageSet") + SESSION_PENDING_MESSAGE_SET, + @SerialName("session/pendingMessageRemoved") + SESSION_PENDING_MESSAGE_REMOVED, + @SerialName("session/queuedMessagesReordered") + SESSION_QUEUED_MESSAGES_REORDERED, + @SerialName("session/inputRequested") + SESSION_INPUT_REQUESTED, + @SerialName("session/inputAnswerChanged") + SESSION_INPUT_ANSWER_CHANGED, + @SerialName("session/inputCompleted") + SESSION_INPUT_COMPLETED, @SerialName("session/customizationsChanged") SESSION_CUSTOMIZATIONS_CHANGED, @SerialName("session/customizationToggled") @@ -106,8 +98,8 @@ enum class ActionType { SESSION_CUSTOMIZATION_REMOVED, @SerialName("session/mcpServerStateChanged") SESSION_MCP_SERVER_STATE_CHANGED, - @SerialName("chat/truncated") - CHAT_TRUNCATED, + @SerialName("session/truncated") + SESSION_TRUNCATED, @SerialName("session/isReadChanged") SESSION_IS_READ_CHANGED, @SerialName("session/isArchivedChanged") @@ -227,50 +219,7 @@ data class SessionCreationFailedAction( ) @Serializable -data class SessionChatAddedAction( - val type: ActionType, - /** - * The full summary of the newly added (or upserted) chat. - */ - val summary: ChatSummary -) - -@Serializable -data class SessionChatRemovedAction( - val type: ActionType, - /** - * The URI of the chat to remove. - */ - val chat: String -) - -@Serializable -data class SessionChatUpdatedAction( - val type: ActionType, - /** - * The URI of the chat whose summary changed. - */ - val chat: String, - /** - * Mutable summary fields that changed; omitted fields are unchanged. - * - * Identity fields (`resource`) never change and MUST be omitted by - * senders; receivers SHOULD ignore them if present. - */ - val changes: PartialChatSummary -) - -@Serializable -data class SessionDefaultChatChangedAction( - val type: ActionType, - /** - * New default chat URI, or `undefined` to clear the hint. - */ - val defaultChat: String? = null -) - -@Serializable -data class ChatTurnStartedAction( +data class SessionTurnStartedAction( val type: ActionType, /** * Turn identifier @@ -287,7 +236,7 @@ data class ChatTurnStartedAction( ) @Serializable -data class ChatDeltaAction( +data class SessionDeltaAction( val type: ActionType, /** * Turn identifier @@ -304,7 +253,7 @@ data class ChatDeltaAction( ) @Serializable -data class ChatResponsePartAction( +data class SessionResponsePartAction( val type: ActionType, /** * Turn identifier @@ -317,7 +266,7 @@ data class ChatResponsePartAction( ) @Serializable -data class ChatToolCallStartAction( +data class SessionToolCallStartAction( /** * Turn identifier */ @@ -353,7 +302,7 @@ data class ChatToolCallStartAction( ) @Serializable -data class ChatToolCallDeltaAction( +data class SessionToolCallDeltaAction( /** * Turn identifier */ @@ -384,7 +333,7 @@ data class ChatToolCallDeltaAction( ) @Serializable -data class ChatToolCallReadyAction( +data class SessionToolCallReadyAction( /** * Turn identifier */ @@ -441,9 +390,9 @@ data class ChatToolCallReadyAction( * Client approves or denies a pending tool call (merged approved + denied variants). */ @Serializable -data class ChatToolCallConfirmedAction( +data class SessionToolCallConfirmedAction( /** Action type discriminant */ - val type: ActionType = ActionType.CHAT_TOOL_CALL_CONFIRMED, + val type: ActionType = ActionType.SESSION_TOOL_CALL_CONFIRMED, /** Turn identifier */ val turnId: String, /** Tool call identifier */ @@ -467,7 +416,7 @@ data class ChatToolCallConfirmedAction( ) @Serializable -data class ChatToolCallCompleteAction( +data class SessionToolCallCompleteAction( /** * Turn identifier */ @@ -498,7 +447,7 @@ data class ChatToolCallCompleteAction( ) @Serializable -data class ChatToolCallResultConfirmedAction( +data class SessionToolCallResultConfirmedAction( /** * Turn identifier */ @@ -525,34 +474,7 @@ data class ChatToolCallResultConfirmedAction( ) @Serializable -data class ChatToolCallContentChangedAction( - /** - * Turn identifier - */ - val turnId: String, - /** - * Tool call identifier - */ - val toolCallId: String, - /** - * Additional provider-specific metadata for this tool call. - * - * Clients MAY look for well-known keys here to provide enhanced UI. - * For example, a `ptyTerminal` key with `{ input: string; output: string }` - * indicates the tool operated on a terminal (both `input` and `output` may - * contain escape sequences). - */ - @SerialName("_meta") - val meta: Map? = null, - val type: ActionType, - /** - * The current partial content for the running tool call - */ - val content: List -) - -@Serializable -data class ChatTurnCompleteAction( +data class SessionTurnCompleteAction( val type: ActionType, /** * Turn identifier @@ -561,7 +483,7 @@ data class ChatTurnCompleteAction( ) @Serializable -data class ChatTurnCancelledAction( +data class SessionTurnCancelledAction( val type: ActionType, /** * Turn identifier @@ -570,7 +492,7 @@ data class ChatTurnCancelledAction( ) @Serializable -data class ChatErrorAction( +data class SessionErrorAction( val type: ActionType, /** * Turn identifier @@ -592,7 +514,7 @@ data class SessionTitleChangedAction( ) @Serializable -data class ChatUsageAction( +data class SessionUsageAction( val type: ActionType, /** * Turn identifier @@ -605,7 +527,7 @@ data class ChatUsageAction( ) @Serializable -data class ChatReasoningAction( +data class SessionReasoningAction( val type: ActionType, /** * Turn identifier @@ -704,7 +626,7 @@ data class SessionActiveClientToolsChangedAction( ) @Serializable -data class ChatPendingMessageSetAction( +data class SessionPendingMessageSetAction( val type: ActionType, /** * Whether this is a steering or queued message @@ -721,7 +643,7 @@ data class ChatPendingMessageSetAction( ) @Serializable -data class ChatPendingMessageRemovedAction( +data class SessionPendingMessageRemovedAction( val type: ActionType, /** * Whether this is a steering or queued message @@ -734,7 +656,7 @@ data class ChatPendingMessageRemovedAction( ) @Serializable -data class ChatQueuedMessagesReorderedAction( +data class SessionQueuedMessagesReorderedAction( val type: ActionType, /** * Queued message IDs in the desired order @@ -743,16 +665,16 @@ data class ChatQueuedMessagesReorderedAction( ) @Serializable -data class ChatInputRequestedAction( +data class SessionInputRequestedAction( val type: ActionType, /** * Input request to create or replace */ - val request: ChatInputRequest + val request: SessionInputRequest ) @Serializable -data class ChatInputAnswerChangedAction( +data class SessionInputAnswerChangedAction( val type: ActionType, /** * Input request identifier @@ -765,11 +687,11 @@ data class ChatInputAnswerChangedAction( /** * Updated answer, or `undefined` to clear an answer draft */ - val answer: ChatInputAnswer? = null + val answer: SessionInputAnswer? = null ) @Serializable -data class ChatInputCompletedAction( +data class SessionInputCompletedAction( val type: ActionType, /** * Input request identifier @@ -778,11 +700,11 @@ data class ChatInputCompletedAction( /** * Completion outcome */ - val response: ChatInputResponseKind, + val response: SessionInputResponseKind, /** * Optional final answer replacement, keyed by question ID */ - val answers: Map? = null + val answers: Map? = null ) @Serializable @@ -845,7 +767,7 @@ data class SessionMcpServerStateChangedAction( ) @Serializable -data class ChatTruncatedAction( +data class SessionTruncatedAction( val type: ActionType, /** * Keep turns up to and including this turn. Omit to clear all turns. @@ -876,6 +798,33 @@ data class SessionMetaChangedAction( val meta: Map? = null ) +@Serializable +data class SessionToolCallContentChangedAction( + /** + * Turn identifier + */ + val turnId: String, + /** + * Tool call identifier + */ + val toolCallId: String, + /** + * Additional provider-specific metadata for this tool call. + * + * Clients MAY look for well-known keys here to provide enhanced UI. + * For example, a `ptyTerminal` key with `{ input: string; output: string }` + * indicates the tool operated on a terminal (both `input` and `output` may + * contain escape sequences). + */ + @SerialName("_meta") + val meta: Map? = null, + val type: ActionType, + /** + * The current partial content for the running tool call + */ + val content: List +) + @Serializable data class ChangesetStatusChangedAction( val type: ActionType, @@ -1159,52 +1108,6 @@ data class ResourceWatchChangedAction( val changes: JsonElement ) -// ─── Partial Summary Types ────────────────────────────────────────────────── - -@Serializable -data class PartialChatSummary( - /** - * Chat URI - */ - val resource: String? = null, - /** - * Chat title - */ - val title: String? = null, - /** - * Current chat status (reuses SessionStatus shape) - */ - val status: SessionStatus? = null, - /** - * Human-readable description of what the chat is currently doing - */ - val activity: String? = null, - /** - * Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - */ - val modifiedAt: String? = null, - /** - * Optional per-chat model override (defaults to the session's model) - */ - val model: ModelSelection? = null, - /** - * Optional per-chat agent override (defaults to the session's agent) - */ - val agent: AgentSelection? = null, - /** - * How this chat came into existence - */ - val origin: ChatOrigin? = null, - /** - * Optional per-chat working directory. - * - * If absent, the chat inherits - * {@link SessionSummary.workingDirectory | the session's working directory}. - * See {@link ChatState.workingDirectory} for usage notes. - */ - val workingDirectory: String? = null -) - // ─── StateAction Union ────────────────────────────────────────────────────── /** @@ -1223,26 +1126,21 @@ sealed interface StateAction @JvmInline value class StateActionRootActiveSessionsChanged(val value: RootActiveSessionsChangedAction) : StateAction @JvmInline value class StateActionSessionReady(val value: SessionReadyAction) : StateAction @JvmInline value class StateActionSessionCreationFailed(val value: SessionCreationFailedAction) : StateAction -@JvmInline value class StateActionSessionChatAdded(val value: SessionChatAddedAction) : StateAction -@JvmInline value class StateActionSessionChatRemoved(val value: SessionChatRemovedAction) : StateAction -@JvmInline value class StateActionSessionChatUpdated(val value: SessionChatUpdatedAction) : StateAction -@JvmInline value class StateActionSessionDefaultChatChanged(val value: SessionDefaultChatChangedAction) : StateAction -@JvmInline value class StateActionChatTurnStarted(val value: ChatTurnStartedAction) : StateAction -@JvmInline value class StateActionChatDelta(val value: ChatDeltaAction) : StateAction -@JvmInline value class StateActionChatResponsePart(val value: ChatResponsePartAction) : StateAction -@JvmInline value class StateActionChatToolCallStart(val value: ChatToolCallStartAction) : StateAction -@JvmInline value class StateActionChatToolCallDelta(val value: ChatToolCallDeltaAction) : StateAction -@JvmInline value class StateActionChatToolCallReady(val value: ChatToolCallReadyAction) : StateAction -@JvmInline value class StateActionChatToolCallConfirmed(val value: ChatToolCallConfirmedAction) : StateAction -@JvmInline value class StateActionChatToolCallComplete(val value: ChatToolCallCompleteAction) : StateAction -@JvmInline value class StateActionChatToolCallResultConfirmed(val value: ChatToolCallResultConfirmedAction) : StateAction -@JvmInline value class StateActionChatToolCallContentChanged(val value: ChatToolCallContentChangedAction) : StateAction -@JvmInline value class StateActionChatTurnComplete(val value: ChatTurnCompleteAction) : StateAction -@JvmInline value class StateActionChatTurnCancelled(val value: ChatTurnCancelledAction) : StateAction -@JvmInline value class StateActionChatError(val value: ChatErrorAction) : StateAction +@JvmInline value class StateActionSessionTurnStarted(val value: SessionTurnStartedAction) : StateAction +@JvmInline value class StateActionSessionDelta(val value: SessionDeltaAction) : StateAction +@JvmInline value class StateActionSessionResponsePart(val value: SessionResponsePartAction) : StateAction +@JvmInline value class StateActionSessionToolCallStart(val value: SessionToolCallStartAction) : StateAction +@JvmInline value class StateActionSessionToolCallDelta(val value: SessionToolCallDeltaAction) : StateAction +@JvmInline value class StateActionSessionToolCallReady(val value: SessionToolCallReadyAction) : StateAction +@JvmInline value class StateActionSessionToolCallConfirmed(val value: SessionToolCallConfirmedAction) : StateAction +@JvmInline value class StateActionSessionToolCallComplete(val value: SessionToolCallCompleteAction) : StateAction +@JvmInline value class StateActionSessionToolCallResultConfirmed(val value: SessionToolCallResultConfirmedAction) : StateAction +@JvmInline value class StateActionSessionTurnComplete(val value: SessionTurnCompleteAction) : StateAction +@JvmInline value class StateActionSessionTurnCancelled(val value: SessionTurnCancelledAction) : StateAction +@JvmInline value class StateActionSessionError(val value: SessionErrorAction) : StateAction @JvmInline value class StateActionSessionTitleChanged(val value: SessionTitleChangedAction) : StateAction -@JvmInline value class StateActionChatUsage(val value: ChatUsageAction) : StateAction -@JvmInline value class StateActionChatReasoning(val value: ChatReasoningAction) : StateAction +@JvmInline value class StateActionSessionUsage(val value: SessionUsageAction) : StateAction +@JvmInline value class StateActionSessionReasoning(val value: SessionReasoningAction) : StateAction @JvmInline value class StateActionSessionModelChanged(val value: SessionModelChangedAction) : StateAction @JvmInline value class StateActionSessionAgentChanged(val value: SessionAgentChangedAction) : StateAction @JvmInline value class StateActionSessionIsReadChanged(val value: SessionIsReadChangedAction) : StateAction @@ -1252,20 +1150,21 @@ sealed interface StateAction @JvmInline value class StateActionSessionServerToolsChanged(val value: SessionServerToolsChangedAction) : StateAction @JvmInline value class StateActionSessionActiveClientChanged(val value: SessionActiveClientChangedAction) : StateAction @JvmInline value class StateActionSessionActiveClientToolsChanged(val value: SessionActiveClientToolsChangedAction) : StateAction -@JvmInline value class StateActionChatPendingMessageSet(val value: ChatPendingMessageSetAction) : StateAction -@JvmInline value class StateActionChatPendingMessageRemoved(val value: ChatPendingMessageRemovedAction) : StateAction -@JvmInline value class StateActionChatQueuedMessagesReordered(val value: ChatQueuedMessagesReorderedAction) : StateAction -@JvmInline value class StateActionChatInputRequested(val value: ChatInputRequestedAction) : StateAction -@JvmInline value class StateActionChatInputAnswerChanged(val value: ChatInputAnswerChangedAction) : StateAction -@JvmInline value class StateActionChatInputCompleted(val value: ChatInputCompletedAction) : StateAction +@JvmInline value class StateActionSessionPendingMessageSet(val value: SessionPendingMessageSetAction) : StateAction +@JvmInline value class StateActionSessionPendingMessageRemoved(val value: SessionPendingMessageRemovedAction) : StateAction +@JvmInline value class StateActionSessionQueuedMessagesReordered(val value: SessionQueuedMessagesReorderedAction) : StateAction +@JvmInline value class StateActionSessionInputRequested(val value: SessionInputRequestedAction) : StateAction +@JvmInline value class StateActionSessionInputAnswerChanged(val value: SessionInputAnswerChangedAction) : StateAction +@JvmInline value class StateActionSessionInputCompleted(val value: SessionInputCompletedAction) : StateAction @JvmInline value class StateActionSessionCustomizationsChanged(val value: SessionCustomizationsChangedAction) : StateAction @JvmInline value class StateActionSessionCustomizationToggled(val value: SessionCustomizationToggledAction) : StateAction @JvmInline value class StateActionSessionCustomizationUpdated(val value: SessionCustomizationUpdatedAction) : StateAction @JvmInline value class StateActionSessionCustomizationRemoved(val value: SessionCustomizationRemovedAction) : StateAction @JvmInline value class StateActionSessionMcpServerStateChanged(val value: SessionMcpServerStateChangedAction) : StateAction -@JvmInline value class StateActionChatTruncated(val value: ChatTruncatedAction) : StateAction +@JvmInline value class StateActionSessionTruncated(val value: SessionTruncatedAction) : StateAction @JvmInline value class StateActionSessionConfigChanged(val value: SessionConfigChangedAction) : StateAction @JvmInline value class StateActionSessionMetaChanged(val value: SessionMetaChangedAction) : StateAction +@JvmInline value class StateActionSessionToolCallContentChanged(val value: SessionToolCallContentChangedAction) : StateAction @JvmInline value class StateActionChangesetStatusChanged(val value: ChangesetStatusChangedAction) : StateAction @JvmInline value class StateActionChangesetFileSet(val value: ChangesetFileSetAction) : StateAction @JvmInline value class StateActionChangesetFileRemoved(val value: ChangesetFileRemovedAction) : StateAction @@ -1310,26 +1209,21 @@ internal object StateActionSerializer : KSerializer { "root/activeSessionsChanged" -> StateActionRootActiveSessionsChanged(input.json.decodeFromJsonElement(RootActiveSessionsChangedAction.serializer(), element)) "session/ready" -> StateActionSessionReady(input.json.decodeFromJsonElement(SessionReadyAction.serializer(), element)) "session/creationFailed" -> StateActionSessionCreationFailed(input.json.decodeFromJsonElement(SessionCreationFailedAction.serializer(), element)) - "session/chatAdded" -> StateActionSessionChatAdded(input.json.decodeFromJsonElement(SessionChatAddedAction.serializer(), element)) - "session/chatRemoved" -> StateActionSessionChatRemoved(input.json.decodeFromJsonElement(SessionChatRemovedAction.serializer(), element)) - "session/chatUpdated" -> StateActionSessionChatUpdated(input.json.decodeFromJsonElement(SessionChatUpdatedAction.serializer(), element)) - "session/defaultChatChanged" -> StateActionSessionDefaultChatChanged(input.json.decodeFromJsonElement(SessionDefaultChatChangedAction.serializer(), element)) - "chat/turnStarted" -> StateActionChatTurnStarted(input.json.decodeFromJsonElement(ChatTurnStartedAction.serializer(), element)) - "chat/delta" -> StateActionChatDelta(input.json.decodeFromJsonElement(ChatDeltaAction.serializer(), element)) - "chat/responsePart" -> StateActionChatResponsePart(input.json.decodeFromJsonElement(ChatResponsePartAction.serializer(), element)) - "chat/toolCallStart" -> StateActionChatToolCallStart(input.json.decodeFromJsonElement(ChatToolCallStartAction.serializer(), element)) - "chat/toolCallDelta" -> StateActionChatToolCallDelta(input.json.decodeFromJsonElement(ChatToolCallDeltaAction.serializer(), element)) - "chat/toolCallReady" -> StateActionChatToolCallReady(input.json.decodeFromJsonElement(ChatToolCallReadyAction.serializer(), element)) - "chat/toolCallConfirmed" -> StateActionChatToolCallConfirmed(input.json.decodeFromJsonElement(ChatToolCallConfirmedAction.serializer(), element)) - "chat/toolCallComplete" -> StateActionChatToolCallComplete(input.json.decodeFromJsonElement(ChatToolCallCompleteAction.serializer(), element)) - "chat/toolCallResultConfirmed" -> StateActionChatToolCallResultConfirmed(input.json.decodeFromJsonElement(ChatToolCallResultConfirmedAction.serializer(), element)) - "chat/toolCallContentChanged" -> StateActionChatToolCallContentChanged(input.json.decodeFromJsonElement(ChatToolCallContentChangedAction.serializer(), element)) - "chat/turnComplete" -> StateActionChatTurnComplete(input.json.decodeFromJsonElement(ChatTurnCompleteAction.serializer(), element)) - "chat/turnCancelled" -> StateActionChatTurnCancelled(input.json.decodeFromJsonElement(ChatTurnCancelledAction.serializer(), element)) - "chat/error" -> StateActionChatError(input.json.decodeFromJsonElement(ChatErrorAction.serializer(), element)) + "session/turnStarted" -> StateActionSessionTurnStarted(input.json.decodeFromJsonElement(SessionTurnStartedAction.serializer(), element)) + "session/delta" -> StateActionSessionDelta(input.json.decodeFromJsonElement(SessionDeltaAction.serializer(), element)) + "session/responsePart" -> StateActionSessionResponsePart(input.json.decodeFromJsonElement(SessionResponsePartAction.serializer(), element)) + "session/toolCallStart" -> StateActionSessionToolCallStart(input.json.decodeFromJsonElement(SessionToolCallStartAction.serializer(), element)) + "session/toolCallDelta" -> StateActionSessionToolCallDelta(input.json.decodeFromJsonElement(SessionToolCallDeltaAction.serializer(), element)) + "session/toolCallReady" -> StateActionSessionToolCallReady(input.json.decodeFromJsonElement(SessionToolCallReadyAction.serializer(), element)) + "session/toolCallConfirmed" -> StateActionSessionToolCallConfirmed(input.json.decodeFromJsonElement(SessionToolCallConfirmedAction.serializer(), element)) + "session/toolCallComplete" -> StateActionSessionToolCallComplete(input.json.decodeFromJsonElement(SessionToolCallCompleteAction.serializer(), element)) + "session/toolCallResultConfirmed" -> StateActionSessionToolCallResultConfirmed(input.json.decodeFromJsonElement(SessionToolCallResultConfirmedAction.serializer(), element)) + "session/turnComplete" -> StateActionSessionTurnComplete(input.json.decodeFromJsonElement(SessionTurnCompleteAction.serializer(), element)) + "session/turnCancelled" -> StateActionSessionTurnCancelled(input.json.decodeFromJsonElement(SessionTurnCancelledAction.serializer(), element)) + "session/error" -> StateActionSessionError(input.json.decodeFromJsonElement(SessionErrorAction.serializer(), element)) "session/titleChanged" -> StateActionSessionTitleChanged(input.json.decodeFromJsonElement(SessionTitleChangedAction.serializer(), element)) - "chat/usage" -> StateActionChatUsage(input.json.decodeFromJsonElement(ChatUsageAction.serializer(), element)) - "chat/reasoning" -> StateActionChatReasoning(input.json.decodeFromJsonElement(ChatReasoningAction.serializer(), element)) + "session/usage" -> StateActionSessionUsage(input.json.decodeFromJsonElement(SessionUsageAction.serializer(), element)) + "session/reasoning" -> StateActionSessionReasoning(input.json.decodeFromJsonElement(SessionReasoningAction.serializer(), element)) "session/modelChanged" -> StateActionSessionModelChanged(input.json.decodeFromJsonElement(SessionModelChangedAction.serializer(), element)) "session/agentChanged" -> StateActionSessionAgentChanged(input.json.decodeFromJsonElement(SessionAgentChangedAction.serializer(), element)) "session/isReadChanged" -> StateActionSessionIsReadChanged(input.json.decodeFromJsonElement(SessionIsReadChangedAction.serializer(), element)) @@ -1339,20 +1233,21 @@ internal object StateActionSerializer : KSerializer { "session/serverToolsChanged" -> StateActionSessionServerToolsChanged(input.json.decodeFromJsonElement(SessionServerToolsChangedAction.serializer(), element)) "session/activeClientChanged" -> StateActionSessionActiveClientChanged(input.json.decodeFromJsonElement(SessionActiveClientChangedAction.serializer(), element)) "session/activeClientToolsChanged" -> StateActionSessionActiveClientToolsChanged(input.json.decodeFromJsonElement(SessionActiveClientToolsChangedAction.serializer(), element)) - "chat/pendingMessageSet" -> StateActionChatPendingMessageSet(input.json.decodeFromJsonElement(ChatPendingMessageSetAction.serializer(), element)) - "chat/pendingMessageRemoved" -> StateActionChatPendingMessageRemoved(input.json.decodeFromJsonElement(ChatPendingMessageRemovedAction.serializer(), element)) - "chat/queuedMessagesReordered" -> StateActionChatQueuedMessagesReordered(input.json.decodeFromJsonElement(ChatQueuedMessagesReorderedAction.serializer(), element)) - "chat/inputRequested" -> StateActionChatInputRequested(input.json.decodeFromJsonElement(ChatInputRequestedAction.serializer(), element)) - "chat/inputAnswerChanged" -> StateActionChatInputAnswerChanged(input.json.decodeFromJsonElement(ChatInputAnswerChangedAction.serializer(), element)) - "chat/inputCompleted" -> StateActionChatInputCompleted(input.json.decodeFromJsonElement(ChatInputCompletedAction.serializer(), element)) + "session/pendingMessageSet" -> StateActionSessionPendingMessageSet(input.json.decodeFromJsonElement(SessionPendingMessageSetAction.serializer(), element)) + "session/pendingMessageRemoved" -> StateActionSessionPendingMessageRemoved(input.json.decodeFromJsonElement(SessionPendingMessageRemovedAction.serializer(), element)) + "session/queuedMessagesReordered" -> StateActionSessionQueuedMessagesReordered(input.json.decodeFromJsonElement(SessionQueuedMessagesReorderedAction.serializer(), element)) + "session/inputRequested" -> StateActionSessionInputRequested(input.json.decodeFromJsonElement(SessionInputRequestedAction.serializer(), element)) + "session/inputAnswerChanged" -> StateActionSessionInputAnswerChanged(input.json.decodeFromJsonElement(SessionInputAnswerChangedAction.serializer(), element)) + "session/inputCompleted" -> StateActionSessionInputCompleted(input.json.decodeFromJsonElement(SessionInputCompletedAction.serializer(), element)) "session/customizationsChanged" -> StateActionSessionCustomizationsChanged(input.json.decodeFromJsonElement(SessionCustomizationsChangedAction.serializer(), element)) "session/customizationToggled" -> StateActionSessionCustomizationToggled(input.json.decodeFromJsonElement(SessionCustomizationToggledAction.serializer(), element)) "session/customizationUpdated" -> StateActionSessionCustomizationUpdated(input.json.decodeFromJsonElement(SessionCustomizationUpdatedAction.serializer(), element)) "session/customizationRemoved" -> StateActionSessionCustomizationRemoved(input.json.decodeFromJsonElement(SessionCustomizationRemovedAction.serializer(), element)) "session/mcpServerStateChanged" -> StateActionSessionMcpServerStateChanged(input.json.decodeFromJsonElement(SessionMcpServerStateChangedAction.serializer(), element)) - "chat/truncated" -> StateActionChatTruncated(input.json.decodeFromJsonElement(ChatTruncatedAction.serializer(), element)) + "session/truncated" -> StateActionSessionTruncated(input.json.decodeFromJsonElement(SessionTruncatedAction.serializer(), element)) "session/configChanged" -> StateActionSessionConfigChanged(input.json.decodeFromJsonElement(SessionConfigChangedAction.serializer(), element)) "session/metaChanged" -> StateActionSessionMetaChanged(input.json.decodeFromJsonElement(SessionMetaChangedAction.serializer(), element)) + "session/toolCallContentChanged" -> StateActionSessionToolCallContentChanged(input.json.decodeFromJsonElement(SessionToolCallContentChangedAction.serializer(), element)) "changeset/statusChanged" -> StateActionChangesetStatusChanged(input.json.decodeFromJsonElement(ChangesetStatusChangedAction.serializer(), element)) "changeset/fileSet" -> StateActionChangesetFileSet(input.json.decodeFromJsonElement(ChangesetFileSetAction.serializer(), element)) "changeset/fileRemoved" -> StateActionChangesetFileRemoved(input.json.decodeFromJsonElement(ChangesetFileRemovedAction.serializer(), element)) @@ -1390,26 +1285,21 @@ internal object StateActionSerializer : KSerializer { is StateActionRootActiveSessionsChanged -> output.json.encodeToJsonElement(RootActiveSessionsChangedAction.serializer(), value.value) is StateActionSessionReady -> output.json.encodeToJsonElement(SessionReadyAction.serializer(), value.value) is StateActionSessionCreationFailed -> output.json.encodeToJsonElement(SessionCreationFailedAction.serializer(), value.value) - is StateActionSessionChatAdded -> output.json.encodeToJsonElement(SessionChatAddedAction.serializer(), value.value) - is StateActionSessionChatRemoved -> output.json.encodeToJsonElement(SessionChatRemovedAction.serializer(), value.value) - is StateActionSessionChatUpdated -> output.json.encodeToJsonElement(SessionChatUpdatedAction.serializer(), value.value) - is StateActionSessionDefaultChatChanged -> output.json.encodeToJsonElement(SessionDefaultChatChangedAction.serializer(), value.value) - is StateActionChatTurnStarted -> output.json.encodeToJsonElement(ChatTurnStartedAction.serializer(), value.value) - is StateActionChatDelta -> output.json.encodeToJsonElement(ChatDeltaAction.serializer(), value.value) - is StateActionChatResponsePart -> output.json.encodeToJsonElement(ChatResponsePartAction.serializer(), value.value) - is StateActionChatToolCallStart -> output.json.encodeToJsonElement(ChatToolCallStartAction.serializer(), value.value) - is StateActionChatToolCallDelta -> output.json.encodeToJsonElement(ChatToolCallDeltaAction.serializer(), value.value) - is StateActionChatToolCallReady -> output.json.encodeToJsonElement(ChatToolCallReadyAction.serializer(), value.value) - is StateActionChatToolCallConfirmed -> output.json.encodeToJsonElement(ChatToolCallConfirmedAction.serializer(), value.value) - is StateActionChatToolCallComplete -> output.json.encodeToJsonElement(ChatToolCallCompleteAction.serializer(), value.value) - is StateActionChatToolCallResultConfirmed -> output.json.encodeToJsonElement(ChatToolCallResultConfirmedAction.serializer(), value.value) - is StateActionChatToolCallContentChanged -> output.json.encodeToJsonElement(ChatToolCallContentChangedAction.serializer(), value.value) - is StateActionChatTurnComplete -> output.json.encodeToJsonElement(ChatTurnCompleteAction.serializer(), value.value) - is StateActionChatTurnCancelled -> output.json.encodeToJsonElement(ChatTurnCancelledAction.serializer(), value.value) - is StateActionChatError -> output.json.encodeToJsonElement(ChatErrorAction.serializer(), value.value) + is StateActionSessionTurnStarted -> output.json.encodeToJsonElement(SessionTurnStartedAction.serializer(), value.value) + is StateActionSessionDelta -> output.json.encodeToJsonElement(SessionDeltaAction.serializer(), value.value) + is StateActionSessionResponsePart -> output.json.encodeToJsonElement(SessionResponsePartAction.serializer(), value.value) + is StateActionSessionToolCallStart -> output.json.encodeToJsonElement(SessionToolCallStartAction.serializer(), value.value) + is StateActionSessionToolCallDelta -> output.json.encodeToJsonElement(SessionToolCallDeltaAction.serializer(), value.value) + is StateActionSessionToolCallReady -> output.json.encodeToJsonElement(SessionToolCallReadyAction.serializer(), value.value) + is StateActionSessionToolCallConfirmed -> output.json.encodeToJsonElement(SessionToolCallConfirmedAction.serializer(), value.value) + is StateActionSessionToolCallComplete -> output.json.encodeToJsonElement(SessionToolCallCompleteAction.serializer(), value.value) + is StateActionSessionToolCallResultConfirmed -> output.json.encodeToJsonElement(SessionToolCallResultConfirmedAction.serializer(), value.value) + is StateActionSessionTurnComplete -> output.json.encodeToJsonElement(SessionTurnCompleteAction.serializer(), value.value) + is StateActionSessionTurnCancelled -> output.json.encodeToJsonElement(SessionTurnCancelledAction.serializer(), value.value) + is StateActionSessionError -> output.json.encodeToJsonElement(SessionErrorAction.serializer(), value.value) is StateActionSessionTitleChanged -> output.json.encodeToJsonElement(SessionTitleChangedAction.serializer(), value.value) - is StateActionChatUsage -> output.json.encodeToJsonElement(ChatUsageAction.serializer(), value.value) - is StateActionChatReasoning -> output.json.encodeToJsonElement(ChatReasoningAction.serializer(), value.value) + is StateActionSessionUsage -> output.json.encodeToJsonElement(SessionUsageAction.serializer(), value.value) + is StateActionSessionReasoning -> output.json.encodeToJsonElement(SessionReasoningAction.serializer(), value.value) is StateActionSessionModelChanged -> output.json.encodeToJsonElement(SessionModelChangedAction.serializer(), value.value) is StateActionSessionAgentChanged -> output.json.encodeToJsonElement(SessionAgentChangedAction.serializer(), value.value) is StateActionSessionIsReadChanged -> output.json.encodeToJsonElement(SessionIsReadChangedAction.serializer(), value.value) @@ -1419,20 +1309,21 @@ internal object StateActionSerializer : KSerializer { is StateActionSessionServerToolsChanged -> output.json.encodeToJsonElement(SessionServerToolsChangedAction.serializer(), value.value) is StateActionSessionActiveClientChanged -> output.json.encodeToJsonElement(SessionActiveClientChangedAction.serializer(), value.value) is StateActionSessionActiveClientToolsChanged -> output.json.encodeToJsonElement(SessionActiveClientToolsChangedAction.serializer(), value.value) - is StateActionChatPendingMessageSet -> output.json.encodeToJsonElement(ChatPendingMessageSetAction.serializer(), value.value) - is StateActionChatPendingMessageRemoved -> output.json.encodeToJsonElement(ChatPendingMessageRemovedAction.serializer(), value.value) - is StateActionChatQueuedMessagesReordered -> output.json.encodeToJsonElement(ChatQueuedMessagesReorderedAction.serializer(), value.value) - is StateActionChatInputRequested -> output.json.encodeToJsonElement(ChatInputRequestedAction.serializer(), value.value) - is StateActionChatInputAnswerChanged -> output.json.encodeToJsonElement(ChatInputAnswerChangedAction.serializer(), value.value) - is StateActionChatInputCompleted -> output.json.encodeToJsonElement(ChatInputCompletedAction.serializer(), value.value) + is StateActionSessionPendingMessageSet -> output.json.encodeToJsonElement(SessionPendingMessageSetAction.serializer(), value.value) + is StateActionSessionPendingMessageRemoved -> output.json.encodeToJsonElement(SessionPendingMessageRemovedAction.serializer(), value.value) + is StateActionSessionQueuedMessagesReordered -> output.json.encodeToJsonElement(SessionQueuedMessagesReorderedAction.serializer(), value.value) + is StateActionSessionInputRequested -> output.json.encodeToJsonElement(SessionInputRequestedAction.serializer(), value.value) + is StateActionSessionInputAnswerChanged -> output.json.encodeToJsonElement(SessionInputAnswerChangedAction.serializer(), value.value) + is StateActionSessionInputCompleted -> output.json.encodeToJsonElement(SessionInputCompletedAction.serializer(), value.value) is StateActionSessionCustomizationsChanged -> output.json.encodeToJsonElement(SessionCustomizationsChangedAction.serializer(), value.value) is StateActionSessionCustomizationToggled -> output.json.encodeToJsonElement(SessionCustomizationToggledAction.serializer(), value.value) is StateActionSessionCustomizationUpdated -> output.json.encodeToJsonElement(SessionCustomizationUpdatedAction.serializer(), value.value) is StateActionSessionCustomizationRemoved -> output.json.encodeToJsonElement(SessionCustomizationRemovedAction.serializer(), value.value) is StateActionSessionMcpServerStateChanged -> output.json.encodeToJsonElement(SessionMcpServerStateChangedAction.serializer(), value.value) - is StateActionChatTruncated -> output.json.encodeToJsonElement(ChatTruncatedAction.serializer(), value.value) + is StateActionSessionTruncated -> output.json.encodeToJsonElement(SessionTruncatedAction.serializer(), value.value) is StateActionSessionConfigChanged -> output.json.encodeToJsonElement(SessionConfigChangedAction.serializer(), value.value) is StateActionSessionMetaChanged -> output.json.encodeToJsonElement(SessionMetaChangedAction.serializer(), value.value) + is StateActionSessionToolCallContentChanged -> output.json.encodeToJsonElement(SessionToolCallContentChangedAction.serializer(), value.value) is StateActionChangesetStatusChanged -> output.json.encodeToJsonElement(ChangesetStatusChangedAction.serializer(), value.value) is StateActionChangesetFileSet -> output.json.encodeToJsonElement(ChangesetFileSetAction.serializer(), value.value) is StateActionChangesetFileRemoved -> output.json.encodeToJsonElement(ChangesetFileRemovedAction.serializer(), value.value) 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 08223dfb..e532cba7 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 @@ -330,54 +330,6 @@ data class DisposeSessionParams( val channel: String ) -@Serializable -data class ChatForkSource( - /** - * URI of the existing chat to fork from - */ - val chat: String, - /** - * Turn ID in the source chat; content up to and including this turn's response is copied - */ - val turnId: String -) - -@Serializable -data class CreateChatParams( - /** - * Channel URI this command targets. - */ - val channel: String, - /** - * Chat URI (client-chosen, e.g. `ahp-chat:/`). - */ - val chat: String, - /** - * Optional initial message for the new chat. - */ - val initialMessage: Message? = null, - /** - * Optional per-chat model override. - */ - val model: ModelSelection? = null, - /** - * Optional per-chat agent override. - */ - val agent: AgentSelection? = null, - /** - * Optional source chat and turn to fork from. - */ - val source: ChatForkSource? = null -) - -@Serializable -data class DisposeChatParams( - /** - * Channel URI this command targets. - */ - val channel: String -) - @Serializable data class ListSessionsParams( /** 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 37bd2eea..e7128cda 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 @@ -189,10 +189,7 @@ data class PartialSessionSummary( */ val agent: AgentSelection? = null, /** - * The default working directory URI for this session. Individual chats - * MAY override via {@link ChatSummary.workingDirectory | their own - * `workingDirectory`}; this field acts as the fallback for any chat that - * does not. + * The working directory URI for this session */ val workingDirectory: String? = null, /** diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt index c4dda2d1..be4abbfd 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 @@ -168,21 +168,11 @@ internal object SessionStatusSerializer : KSerializer { SessionStatus(decoder.decodeInt()) } -@Serializable -enum class ChatOriginKind { - @SerialName("user") - USER, - @SerialName("fork") - FORK, - @SerialName("tool") - TOOL -} - /** * Answer lifecycle state. */ @Serializable -enum class ChatInputAnswerState { +enum class SessionInputAnswerState { @SerialName("draft") DRAFT, @SerialName("submitted") @@ -195,7 +185,7 @@ enum class ChatInputAnswerState { * Answer value kind. */ @Serializable -enum class ChatInputAnswerValueKind { +enum class SessionInputAnswerValueKind { @SerialName("text") TEXT, @SerialName("number") @@ -212,7 +202,7 @@ enum class ChatInputAnswerValueKind { * Question/input control kind. */ @Serializable -enum class ChatInputQuestionKind { +enum class SessionInputQuestionKind { @SerialName("text") TEXT, @SerialName("number") @@ -231,7 +221,7 @@ enum class ChatInputQuestionKind { * How a client completed an input request. */ @Serializable -enum class ChatInputResponseKind { +enum class SessionInputResponseKind { @SerialName("accept") ACCEPT, @SerialName("decline") @@ -949,49 +939,27 @@ data class PendingMessage( ) @Serializable -data class ChatState( - /** - * Chat URI - */ - val resource: String, - /** - * Chat title - */ - val title: String, - /** - * Current chat status (reuses SessionStatus shape) - */ - val status: SessionStatus, - /** - * Human-readable description of what the chat is currently doing - */ - val activity: String? = null, +data class SessionState( /** - * Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) + * Lightweight session metadata */ - val modifiedAt: String, + val summary: SessionSummary, /** - * Optional per-chat model override (defaults to the session's model) + * Session initialization state */ - val model: ModelSelection? = null, + val lifecycle: SessionLifecycle, /** - * Optional per-chat agent override (defaults to the session's agent) + * Error details if creation failed */ - val agent: AgentSelection? = null, + val creationError: ErrorInfo? = null, /** - * How this chat came into existence + * Tools provided by the server (agent host) for this session */ - val origin: ChatOrigin? = null, + val serverTools: List? = null, /** - * Optional per-chat working directory. - * - * If absent, the chat inherits - * {@link SessionSummary.workingDirectory | the session's working directory}. - * Hosts MAY override this for individual chats — for example, to give a - * subordinate chat its own git worktree so multiple chats in a session can - * make independent edits that the orchestrator later merges back. + * The client currently providing tools and interactive capabilities to this session */ - val workingDirectory: String? = null, + val activeClient: SessionActiveClient? = null, /** * Completed turns */ @@ -1009,93 +977,9 @@ data class ChatState( */ val queuedMessages: List? = null, /** - * Requests for user input that are currently blocking or informing chat progress - */ - val inputRequests: List? = null, - /** - * Additional provider-specific metadata for this chat. - */ - @SerialName("_meta") - val meta: Map? = null -) - -@Serializable -data class ChatSummary( - /** - * Chat URI - */ - val resource: String, - /** - * Chat title - */ - val title: String, - /** - * Current chat status (reuses SessionStatus shape) - */ - val status: SessionStatus, - /** - * Human-readable description of what the chat is currently doing - */ - val activity: String? = null, - /** - * Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - */ - val modifiedAt: String, - /** - * Optional per-chat model override (defaults to the session's model) - */ - val model: ModelSelection? = null, - /** - * Optional per-chat agent override (defaults to the session's agent) + * Requests for user input that are currently blocking or informing session progress */ - val agent: AgentSelection? = null, - /** - * How this chat came into existence - */ - val origin: ChatOrigin? = null, - /** - * Optional per-chat working directory. - * - * If absent, the chat inherits - * {@link SessionSummary.workingDirectory | the session's working directory}. - * See {@link ChatState.workingDirectory} for usage notes. - */ - val workingDirectory: String? = null -) - -@Serializable -data class SessionState( - /** - * Lightweight session metadata - */ - val summary: SessionSummary, - /** - * Session initialization state - */ - val lifecycle: SessionLifecycle, - /** - * Error details if creation failed - */ - val creationError: ErrorInfo? = null, - /** - * Tools provided by the server (agent host) for this session - */ - val serverTools: List? = null, - /** - * The client currently providing tools and interactive capabilities to this session - */ - val activeClient: SessionActiveClient? = null, - /** - * Catalog of chats in this session. - */ - val chats: List, - /** - * The chat that receives input when the user addresses the session without - * selecting a specific chat. This is a UI routing hint, not a hierarchy - * marker — chats remain equal peers at the protocol level. Hosts MAY change - * this over the session's lifetime. - */ - val defaultChat: String? = null, + val inputRequests: List? = null, /** * Session configuration schema and current values */ @@ -1212,10 +1096,7 @@ data class SessionSummary( */ val agent: AgentSelection? = null, /** - * The default working directory URI for this session. Individual chats - * MAY override via {@link ChatSummary.workingDirectory | their own - * `workingDirectory`}; this field acts as the fallback for any chat that - * does not. + * The working directory URI for this session */ val workingDirectory: String? = null, /** @@ -1353,7 +1234,7 @@ data class Message( ) @Serializable -data class ChatInputOption( +data class SessionInputOption( /** * Stable option identifier; for MCP enum values this is the enum string */ @@ -1373,26 +1254,26 @@ data class ChatInputOption( ) @Serializable -data class ChatInputTextAnswerValue( - val kind: ChatInputAnswerValueKind, +data class SessionInputTextAnswerValue( + val kind: SessionInputAnswerValueKind, val value: String ) @Serializable -data class ChatInputNumberAnswerValue( - val kind: ChatInputAnswerValueKind, +data class SessionInputNumberAnswerValue( + val kind: SessionInputAnswerValueKind, val value: Double ) @Serializable -data class ChatInputBooleanAnswerValue( - val kind: ChatInputAnswerValueKind, +data class SessionInputBooleanAnswerValue( + val kind: SessionInputAnswerValueKind, val value: Boolean ) @Serializable -data class ChatInputSelectedAnswerValue( - val kind: ChatInputAnswerValueKind, +data class SessionInputSelectedAnswerValue( + val kind: SessionInputAnswerValueKind, val value: String, /** * Free-form text entered instead of selecting an option @@ -1401,8 +1282,8 @@ data class ChatInputSelectedAnswerValue( ) @Serializable -data class ChatInputSelectedManyAnswerValue( - val kind: ChatInputAnswerValueKind, +data class SessionInputSelectedManyAnswerValue( + val kind: SessionInputAnswerValueKind, val value: List, /** * Free-form text entered in addition to selected options @@ -1411,23 +1292,23 @@ data class ChatInputSelectedManyAnswerValue( ) @Serializable -data class ChatInputAnswered( +data class SessionInputAnswered( /** * Answer state */ - val state: ChatInputAnswerState, + val state: SessionInputAnswerState, /** * Answer value */ - val value: ChatInputAnswerValue + val value: SessionInputAnswerValue ) @Serializable -data class ChatInputSkipped( +data class SessionInputSkipped( /** * Answer state */ - val state: ChatInputAnswerState, + val state: SessionInputAnswerState, /** * Free-form reason or value captured while skipping, if any */ @@ -1435,7 +1316,7 @@ data class ChatInputSkipped( ) @Serializable -data class ChatInputTextQuestion( +data class SessionInputTextQuestion( /** * Stable question identifier used as the key in `answers` */ @@ -1452,7 +1333,7 @@ data class ChatInputTextQuestion( * Whether the user must answer this question to accept the request */ val required: Boolean? = null, - val kind: ChatInputQuestionKind, + val kind: SessionInputQuestionKind, /** * Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` */ @@ -1472,7 +1353,7 @@ data class ChatInputTextQuestion( ) @Serializable -data class ChatInputNumberQuestion( +data class SessionInputNumberQuestion( /** * Stable question identifier used as the key in `answers` */ @@ -1489,7 +1370,7 @@ data class ChatInputNumberQuestion( * Whether the user must answer this question to accept the request */ val required: Boolean? = null, - val kind: ChatInputQuestionKind, + val kind: SessionInputQuestionKind, /** * Minimum value */ @@ -1505,7 +1386,7 @@ data class ChatInputNumberQuestion( ) @Serializable -data class ChatInputBooleanQuestion( +data class SessionInputBooleanQuestion( /** * Stable question identifier used as the key in `answers` */ @@ -1522,7 +1403,7 @@ data class ChatInputBooleanQuestion( * Whether the user must answer this question to accept the request */ val required: Boolean? = null, - val kind: ChatInputQuestionKind, + val kind: SessionInputQuestionKind, /** * Default boolean value */ @@ -1530,7 +1411,7 @@ data class ChatInputBooleanQuestion( ) @Serializable -data class ChatInputSingleSelectQuestion( +data class SessionInputSingleSelectQuestion( /** * Stable question identifier used as the key in `answers` */ @@ -1547,11 +1428,11 @@ data class ChatInputSingleSelectQuestion( * Whether the user must answer this question to accept the request */ val required: Boolean? = null, - val kind: ChatInputQuestionKind, + val kind: SessionInputQuestionKind, /** * Options the user may select from */ - val options: List, + val options: List, /** * Whether the user may enter text instead of selecting an option */ @@ -1559,7 +1440,7 @@ data class ChatInputSingleSelectQuestion( ) @Serializable -data class ChatInputMultiSelectQuestion( +data class SessionInputMultiSelectQuestion( /** * Stable question identifier used as the key in `answers` */ @@ -1576,11 +1457,11 @@ data class ChatInputMultiSelectQuestion( * Whether the user must answer this question to accept the request */ val required: Boolean? = null, - val kind: ChatInputQuestionKind, + val kind: SessionInputQuestionKind, /** * Options the user may select from */ - val options: List, + val options: List, /** * Whether the user may enter text in addition to selecting options */ @@ -1596,7 +1477,7 @@ data class ChatInputMultiSelectQuestion( ) @Serializable -data class ChatInputRequest( +data class SessionInputRequest( /** * Stable request identifier */ @@ -1612,11 +1493,11 @@ data class ChatInputRequest( /** * Ordered questions to ask the user */ - val questions: List? = null, + val questions: List? = null, /** * Current draft or submitted answers, keyed by question ID */ - val answers: Map? = null + val answers: Map? = null ) @Serializable @@ -1873,7 +1754,7 @@ data class MarkdownResponsePart( */ val kind: ResponsePartKind, /** - * Part identifier, used by `chat/delta` to target this part for content appends + * Part identifier, used by `session/delta` to target this part for content appends */ val id: String, /** @@ -1937,7 +1818,7 @@ data class ReasoningResponsePart( */ val kind: ResponsePartKind, /** - * Part identifier, used by `chat/reasoning` to target this part for content appends + * Part identifier, used by `session/reasoning` to target this part for content appends */ val id: String, /** @@ -3101,7 +2982,7 @@ data class ToolCallClientContributor( * Absent for server-side tools. * * When set, the identified client is responsible for executing the tool and - * dispatching `chat/toolCallComplete` with the result. + * dispatching `session/toolCallComplete` with the result. */ val clientId: String ) @@ -3318,7 +3199,7 @@ data class ErrorInfo( @Serializable data class Snapshot( /** - * The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`) + * The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`) */ val resource: String, /** @@ -3640,60 +3521,6 @@ data class ResourceChange( // ─── Discriminated Unions ─────────────────────────────────────────────────── -@Serializable(with = ChatOriginSerializer::class) -sealed interface ChatOrigin { - @JvmInline value class User(val value: ChatOriginUser) : ChatOrigin - @JvmInline value class Fork(val value: ChatOriginFork) : ChatOrigin - @JvmInline value class Tool(val value: ChatOriginTool) : ChatOrigin - @JvmInline value class Unknown(val raw: JsonObject) : ChatOrigin -} - -@Serializable -data class ChatOriginUser( - val kind: ChatOriginKind = ChatOriginKind.USER, -) - -@Serializable -data class ChatOriginFork( - val kind: ChatOriginKind = ChatOriginKind.FORK, - val chat: String, - val turnId: String, -) - -@Serializable -data class ChatOriginTool( - val kind: ChatOriginKind = ChatOriginKind.TOOL, - val chat: String, - val toolCallId: String, -) - -internal object ChatOriginSerializer : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ChatOrigin") - - override fun deserialize(decoder: Decoder): ChatOrigin { - val input = decoder as? JsonDecoder ?: error("ChatOrigin can only be deserialized from JSON") - val element = input.decodeJsonElement() - val obj = element as? JsonObject ?: error("Expected JsonObject for ChatOrigin") - return when ((obj["kind"] as? JsonPrimitive)?.contentOrNull) { - "user" -> ChatOrigin.User(input.json.decodeFromJsonElement(ChatOriginUser.serializer(), element)) - "fork" -> ChatOrigin.Fork(input.json.decodeFromJsonElement(ChatOriginFork.serializer(), element)) - "tool" -> ChatOrigin.Tool(input.json.decodeFromJsonElement(ChatOriginTool.serializer(), element)) - else -> ChatOrigin.Unknown(obj) - } - } - - override fun serialize(encoder: Encoder, value: ChatOrigin) { - val output = encoder as? JsonEncoder ?: error("ChatOrigin can only be serialized to JSON") - val element: JsonElement = when (value) { - is ChatOrigin.User -> output.json.encodeToJsonElement(ChatOriginUser.serializer(), value.value) - is ChatOrigin.Fork -> output.json.encodeToJsonElement(ChatOriginFork.serializer(), value.value) - is ChatOrigin.Tool -> output.json.encodeToJsonElement(ChatOriginTool.serializer(), value.value) - is ChatOrigin.Unknown -> value.raw - } - output.encodeJsonElement(element) - } -} - @Serializable(with = ResponsePartSerializer::class) sealed interface ResponsePart @@ -3918,21 +3745,21 @@ internal object TerminalContentPartSerializer : KSerializer } } -@Serializable(with = ChatInputQuestionSerializer::class) -sealed interface ChatInputQuestion +@Serializable(with = SessionInputQuestionSerializer::class) +sealed interface SessionInputQuestion @JvmInline -value class ChatInputQuestionText(val value: ChatInputTextQuestion) : ChatInputQuestion +value class SessionInputQuestionText(val value: SessionInputTextQuestion) : SessionInputQuestion @JvmInline -value class ChatInputQuestionNumber(val value: ChatInputNumberQuestion) : ChatInputQuestion +value class SessionInputQuestionNumber(val value: SessionInputNumberQuestion) : SessionInputQuestion @JvmInline -value class ChatInputQuestionBoolean(val value: ChatInputBooleanQuestion) : ChatInputQuestion +value class SessionInputQuestionBoolean(val value: SessionInputBooleanQuestion) : SessionInputQuestion @JvmInline -value class ChatInputQuestionSingleSelect(val value: ChatInputSingleSelectQuestion) : ChatInputQuestion +value class SessionInputQuestionSingleSelect(val value: SessionInputSingleSelectQuestion) : SessionInputQuestion @JvmInline -value class ChatInputQuestionMultiSelect(val value: ChatInputMultiSelectQuestion) : ChatInputQuestion +value class SessionInputQuestionMultiSelect(val value: SessionInputMultiSelectQuestion) : SessionInputQuestion /** - * Forward-compat catch-all for unknown ChatInputQuestion discriminators. + * Forward-compat catch-all for unknown SessionInputQuestion discriminators. * * Older clients may receive newer wire variants they don't recognise; capturing * the raw `JsonObject` lets such payloads round-trip through the client unchanged. @@ -3940,61 +3767,61 @@ value class ChatInputQuestionMultiSelect(val value: ChatInputMultiSelectQuestion * as a no-op, but see `Reducers.kt` for the exact treatment). */ @JvmInline -value class ChatInputQuestionUnknown(val raw: JsonObject) : ChatInputQuestion +value class SessionInputQuestionUnknown(val raw: JsonObject) : SessionInputQuestion -internal object ChatInputQuestionSerializer : KSerializer { +internal object SessionInputQuestionSerializer : KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("ChatInputQuestion") + buildClassSerialDescriptor("SessionInputQuestion") - override fun deserialize(decoder: Decoder): ChatInputQuestion { + override fun deserialize(decoder: Decoder): SessionInputQuestion { val input = decoder as? JsonDecoder - ?: error("ChatInputQuestion can only be deserialized from JSON") + ?: error("SessionInputQuestion can only be deserialized from JSON") val element = input.decodeJsonElement() val obj = element as? JsonObject - ?: error("Expected JsonObject for ChatInputQuestion") + ?: error("Expected JsonObject for SessionInputQuestion") val discriminant = (obj["kind"] as? JsonPrimitive)?.content - ?: return ChatInputQuestionUnknown(obj) + ?: return SessionInputQuestionUnknown(obj) return when (discriminant) { - "text" -> ChatInputQuestionText(input.json.decodeFromJsonElement(ChatInputTextQuestion.serializer(), element)) - "number" -> ChatInputQuestionNumber(input.json.decodeFromJsonElement(ChatInputNumberQuestion.serializer(), element)) - "integer" -> ChatInputQuestionNumber(input.json.decodeFromJsonElement(ChatInputNumberQuestion.serializer(), element)) - "boolean" -> ChatInputQuestionBoolean(input.json.decodeFromJsonElement(ChatInputBooleanQuestion.serializer(), element)) - "single-select" -> ChatInputQuestionSingleSelect(input.json.decodeFromJsonElement(ChatInputSingleSelectQuestion.serializer(), element)) - "multi-select" -> ChatInputQuestionMultiSelect(input.json.decodeFromJsonElement(ChatInputMultiSelectQuestion.serializer(), element)) - else -> ChatInputQuestionUnknown(obj) + "text" -> SessionInputQuestionText(input.json.decodeFromJsonElement(SessionInputTextQuestion.serializer(), element)) + "number" -> SessionInputQuestionNumber(input.json.decodeFromJsonElement(SessionInputNumberQuestion.serializer(), element)) + "integer" -> SessionInputQuestionNumber(input.json.decodeFromJsonElement(SessionInputNumberQuestion.serializer(), element)) + "boolean" -> SessionInputQuestionBoolean(input.json.decodeFromJsonElement(SessionInputBooleanQuestion.serializer(), element)) + "single-select" -> SessionInputQuestionSingleSelect(input.json.decodeFromJsonElement(SessionInputSingleSelectQuestion.serializer(), element)) + "multi-select" -> SessionInputQuestionMultiSelect(input.json.decodeFromJsonElement(SessionInputMultiSelectQuestion.serializer(), element)) + else -> SessionInputQuestionUnknown(obj) } } - override fun serialize(encoder: Encoder, value: ChatInputQuestion) { + override fun serialize(encoder: Encoder, value: SessionInputQuestion) { val output = encoder as? JsonEncoder - ?: error("ChatInputQuestion can only be serialized to JSON") + ?: error("SessionInputQuestion can only be serialized to JSON") val element: JsonElement = when (value) { - is ChatInputQuestionText -> output.json.encodeToJsonElement(ChatInputTextQuestion.serializer(), value.value) - is ChatInputQuestionNumber -> output.json.encodeToJsonElement(ChatInputNumberQuestion.serializer(), value.value) - is ChatInputQuestionBoolean -> output.json.encodeToJsonElement(ChatInputBooleanQuestion.serializer(), value.value) - is ChatInputQuestionSingleSelect -> output.json.encodeToJsonElement(ChatInputSingleSelectQuestion.serializer(), value.value) - is ChatInputQuestionMultiSelect -> output.json.encodeToJsonElement(ChatInputMultiSelectQuestion.serializer(), value.value) - is ChatInputQuestionUnknown -> value.raw + is SessionInputQuestionText -> output.json.encodeToJsonElement(SessionInputTextQuestion.serializer(), value.value) + is SessionInputQuestionNumber -> output.json.encodeToJsonElement(SessionInputNumberQuestion.serializer(), value.value) + is SessionInputQuestionBoolean -> output.json.encodeToJsonElement(SessionInputBooleanQuestion.serializer(), value.value) + is SessionInputQuestionSingleSelect -> output.json.encodeToJsonElement(SessionInputSingleSelectQuestion.serializer(), value.value) + is SessionInputQuestionMultiSelect -> output.json.encodeToJsonElement(SessionInputMultiSelectQuestion.serializer(), value.value) + is SessionInputQuestionUnknown -> value.raw } output.encodeJsonElement(element) } } -@Serializable(with = ChatInputAnswerValueSerializer::class) -sealed interface ChatInputAnswerValue +@Serializable(with = SessionInputAnswerValueSerializer::class) +sealed interface SessionInputAnswerValue @JvmInline -value class ChatInputAnswerValueText(val value: ChatInputTextAnswerValue) : ChatInputAnswerValue +value class SessionInputAnswerValueText(val value: SessionInputTextAnswerValue) : SessionInputAnswerValue @JvmInline -value class ChatInputAnswerValueNumber(val value: ChatInputNumberAnswerValue) : ChatInputAnswerValue +value class SessionInputAnswerValueNumber(val value: SessionInputNumberAnswerValue) : SessionInputAnswerValue @JvmInline -value class ChatInputAnswerValueBoolean(val value: ChatInputBooleanAnswerValue) : ChatInputAnswerValue +value class SessionInputAnswerValueBoolean(val value: SessionInputBooleanAnswerValue) : SessionInputAnswerValue @JvmInline -value class ChatInputAnswerValueSelected(val value: ChatInputSelectedAnswerValue) : ChatInputAnswerValue +value class SessionInputAnswerValueSelected(val value: SessionInputSelectedAnswerValue) : SessionInputAnswerValue @JvmInline -value class ChatInputAnswerValueSelectedMany(val value: ChatInputSelectedManyAnswerValue) : ChatInputAnswerValue +value class SessionInputAnswerValueSelectedMany(val value: SessionInputSelectedManyAnswerValue) : SessionInputAnswerValue /** - * Forward-compat catch-all for unknown ChatInputAnswerValue discriminators. + * Forward-compat catch-all for unknown SessionInputAnswerValue discriminators. * * Older clients may receive newer wire variants they don't recognise; capturing * the raw `JsonObject` lets such payloads round-trip through the client unchanged. @@ -4002,54 +3829,54 @@ value class ChatInputAnswerValueSelectedMany(val value: ChatInputSelectedManyAns * as a no-op, but see `Reducers.kt` for the exact treatment). */ @JvmInline -value class ChatInputAnswerValueUnknown(val raw: JsonObject) : ChatInputAnswerValue +value class SessionInputAnswerValueUnknown(val raw: JsonObject) : SessionInputAnswerValue -internal object ChatInputAnswerValueSerializer : KSerializer { +internal object SessionInputAnswerValueSerializer : KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("ChatInputAnswerValue") + buildClassSerialDescriptor("SessionInputAnswerValue") - override fun deserialize(decoder: Decoder): ChatInputAnswerValue { + override fun deserialize(decoder: Decoder): SessionInputAnswerValue { val input = decoder as? JsonDecoder - ?: error("ChatInputAnswerValue can only be deserialized from JSON") + ?: error("SessionInputAnswerValue can only be deserialized from JSON") val element = input.decodeJsonElement() val obj = element as? JsonObject - ?: error("Expected JsonObject for ChatInputAnswerValue") + ?: error("Expected JsonObject for SessionInputAnswerValue") val discriminant = (obj["kind"] as? JsonPrimitive)?.content - ?: return ChatInputAnswerValueUnknown(obj) + ?: return SessionInputAnswerValueUnknown(obj) return when (discriminant) { - "text" -> ChatInputAnswerValueText(input.json.decodeFromJsonElement(ChatInputTextAnswerValue.serializer(), element)) - "number" -> ChatInputAnswerValueNumber(input.json.decodeFromJsonElement(ChatInputNumberAnswerValue.serializer(), element)) - "boolean" -> ChatInputAnswerValueBoolean(input.json.decodeFromJsonElement(ChatInputBooleanAnswerValue.serializer(), element)) - "selected" -> ChatInputAnswerValueSelected(input.json.decodeFromJsonElement(ChatInputSelectedAnswerValue.serializer(), element)) - "selected-many" -> ChatInputAnswerValueSelectedMany(input.json.decodeFromJsonElement(ChatInputSelectedManyAnswerValue.serializer(), element)) - else -> ChatInputAnswerValueUnknown(obj) + "text" -> SessionInputAnswerValueText(input.json.decodeFromJsonElement(SessionInputTextAnswerValue.serializer(), element)) + "number" -> SessionInputAnswerValueNumber(input.json.decodeFromJsonElement(SessionInputNumberAnswerValue.serializer(), element)) + "boolean" -> SessionInputAnswerValueBoolean(input.json.decodeFromJsonElement(SessionInputBooleanAnswerValue.serializer(), element)) + "selected" -> SessionInputAnswerValueSelected(input.json.decodeFromJsonElement(SessionInputSelectedAnswerValue.serializer(), element)) + "selected-many" -> SessionInputAnswerValueSelectedMany(input.json.decodeFromJsonElement(SessionInputSelectedManyAnswerValue.serializer(), element)) + else -> SessionInputAnswerValueUnknown(obj) } } - override fun serialize(encoder: Encoder, value: ChatInputAnswerValue) { + override fun serialize(encoder: Encoder, value: SessionInputAnswerValue) { val output = encoder as? JsonEncoder - ?: error("ChatInputAnswerValue can only be serialized to JSON") + ?: error("SessionInputAnswerValue can only be serialized to JSON") val element: JsonElement = when (value) { - is ChatInputAnswerValueText -> output.json.encodeToJsonElement(ChatInputTextAnswerValue.serializer(), value.value) - is ChatInputAnswerValueNumber -> output.json.encodeToJsonElement(ChatInputNumberAnswerValue.serializer(), value.value) - is ChatInputAnswerValueBoolean -> output.json.encodeToJsonElement(ChatInputBooleanAnswerValue.serializer(), value.value) - is ChatInputAnswerValueSelected -> output.json.encodeToJsonElement(ChatInputSelectedAnswerValue.serializer(), value.value) - is ChatInputAnswerValueSelectedMany -> output.json.encodeToJsonElement(ChatInputSelectedManyAnswerValue.serializer(), value.value) - is ChatInputAnswerValueUnknown -> value.raw + is SessionInputAnswerValueText -> output.json.encodeToJsonElement(SessionInputTextAnswerValue.serializer(), value.value) + is SessionInputAnswerValueNumber -> output.json.encodeToJsonElement(SessionInputNumberAnswerValue.serializer(), value.value) + is SessionInputAnswerValueBoolean -> output.json.encodeToJsonElement(SessionInputBooleanAnswerValue.serializer(), value.value) + is SessionInputAnswerValueSelected -> output.json.encodeToJsonElement(SessionInputSelectedAnswerValue.serializer(), value.value) + is SessionInputAnswerValueSelectedMany -> output.json.encodeToJsonElement(SessionInputSelectedManyAnswerValue.serializer(), value.value) + is SessionInputAnswerValueUnknown -> value.raw } output.encodeJsonElement(element) } } -@Serializable(with = ChatInputAnswerSerializer::class) -sealed interface ChatInputAnswer +@Serializable(with = SessionInputAnswerSerializer::class) +sealed interface SessionInputAnswer @JvmInline -value class ChatInputAnswerDraft(val value: ChatInputAnswered) : ChatInputAnswer +value class SessionInputAnswerDraft(val value: SessionInputAnswered) : SessionInputAnswer @JvmInline -value class ChatInputAnswerSkipped(val value: ChatInputSkipped) : ChatInputAnswer +value class SessionInputAnswerSkipped(val value: SessionInputSkipped) : SessionInputAnswer /** - * Forward-compat catch-all for unknown ChatInputAnswer discriminators. + * Forward-compat catch-all for unknown SessionInputAnswer discriminators. * * Older clients may receive newer wire variants they don't recognise; capturing * the raw `JsonObject` lets such payloads round-trip through the client unchanged. @@ -4057,35 +3884,35 @@ value class ChatInputAnswerSkipped(val value: ChatInputSkipped) : ChatInputAnswe * as a no-op, but see `Reducers.kt` for the exact treatment). */ @JvmInline -value class ChatInputAnswerUnknown(val raw: JsonObject) : ChatInputAnswer +value class SessionInputAnswerUnknown(val raw: JsonObject) : SessionInputAnswer -internal object ChatInputAnswerSerializer : KSerializer { +internal object SessionInputAnswerSerializer : KSerializer { override val descriptor: SerialDescriptor = - buildClassSerialDescriptor("ChatInputAnswer") + buildClassSerialDescriptor("SessionInputAnswer") - override fun deserialize(decoder: Decoder): ChatInputAnswer { + override fun deserialize(decoder: Decoder): SessionInputAnswer { val input = decoder as? JsonDecoder - ?: error("ChatInputAnswer can only be deserialized from JSON") + ?: error("SessionInputAnswer can only be deserialized from JSON") val element = input.decodeJsonElement() val obj = element as? JsonObject - ?: error("Expected JsonObject for ChatInputAnswer") + ?: error("Expected JsonObject for SessionInputAnswer") val discriminant = (obj["state"] as? JsonPrimitive)?.content - ?: return ChatInputAnswerUnknown(obj) + ?: return SessionInputAnswerUnknown(obj) return when (discriminant) { - "draft" -> ChatInputAnswerDraft(input.json.decodeFromJsonElement(ChatInputAnswered.serializer(), element)) - "submitted" -> ChatInputAnswerDraft(input.json.decodeFromJsonElement(ChatInputAnswered.serializer(), element)) - "skipped" -> ChatInputAnswerSkipped(input.json.decodeFromJsonElement(ChatInputSkipped.serializer(), element)) - else -> ChatInputAnswerUnknown(obj) + "draft" -> SessionInputAnswerDraft(input.json.decodeFromJsonElement(SessionInputAnswered.serializer(), element)) + "submitted" -> SessionInputAnswerDraft(input.json.decodeFromJsonElement(SessionInputAnswered.serializer(), element)) + "skipped" -> SessionInputAnswerSkipped(input.json.decodeFromJsonElement(SessionInputSkipped.serializer(), element)) + else -> SessionInputAnswerUnknown(obj) } } - override fun serialize(encoder: Encoder, value: ChatInputAnswer) { + override fun serialize(encoder: Encoder, value: SessionInputAnswer) { val output = encoder as? JsonEncoder - ?: error("ChatInputAnswer can only be serialized to JSON") + ?: error("SessionInputAnswer can only be serialized to JSON") val element: JsonElement = when (value) { - is ChatInputAnswerDraft -> output.json.encodeToJsonElement(ChatInputAnswered.serializer(), value.value) - is ChatInputAnswerSkipped -> output.json.encodeToJsonElement(ChatInputSkipped.serializer(), value.value) - is ChatInputAnswerUnknown -> value.raw + is SessionInputAnswerDraft -> output.json.encodeToJsonElement(SessionInputAnswered.serializer(), value.value) + is SessionInputAnswerSkipped -> output.json.encodeToJsonElement(SessionInputSkipped.serializer(), value.value) + is SessionInputAnswerUnknown -> value.raw } output.encodeJsonElement(element) } @@ -4491,14 +4318,13 @@ internal object ToolResultContentSerializer : KSerializer { } /** - * The state payload of a snapshot — root, session, chat, terminal, changeset, + * The state payload of a snapshot — root, session, terminal, changeset, * resource-watch, or annotations state. */ @Serializable(with = SnapshotStateSerializer::class) sealed interface SnapshotState { @JvmInline value class Root(val value: RootState) : SnapshotState @JvmInline value class Session(val value: SessionState) : SnapshotState - @JvmInline value class Chat(val value: ChatState) : SnapshotState @JvmInline value class Terminal(val value: TerminalState) : SnapshotState @JvmInline value class Changeset(val value: ChangesetState) : SnapshotState @JvmInline value class ResourceWatch(val value: ResourceWatchState) : SnapshotState @@ -4540,7 +4366,6 @@ internal object SnapshotStateSerializer : KSerializer { val element: JsonElement = when (value) { is SnapshotState.Root -> output.json.encodeToJsonElement(RootState.serializer(), value.value) is SnapshotState.Session -> output.json.encodeToJsonElement(SessionState.serializer(), value.value) - is SnapshotState.Chat -> output.json.encodeToJsonElement(ChatState.serializer(), value.value) is SnapshotState.Terminal -> output.json.encodeToJsonElement(TerminalState.serializer(), value.value) is SnapshotState.Changeset -> output.json.encodeToJsonElement(ChangesetState.serializer(), value.value) is SnapshotState.ResourceWatch -> output.json.encodeToJsonElement(ResourceWatchState.serializer(), value.value) diff --git a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/DiscriminatedUnionTest.kt b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/DiscriminatedUnionTest.kt index 46792dac..dcd058d7 100644 --- a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/DiscriminatedUnionTest.kt +++ b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/DiscriminatedUnionTest.kt @@ -13,10 +13,10 @@ import com.microsoft.agenthostprotocol.generated.ResponsePartKind import com.microsoft.agenthostprotocol.generated.ResponsePartMarkdown import com.microsoft.agenthostprotocol.generated.ResponsePartReasoning import com.microsoft.agenthostprotocol.generated.ResponsePartUnknown -import com.microsoft.agenthostprotocol.generated.ChatInputNumberQuestion -import com.microsoft.agenthostprotocol.generated.ChatInputQuestion -import com.microsoft.agenthostprotocol.generated.ChatInputQuestionNumber -import com.microsoft.agenthostprotocol.generated.ChatInputQuestionKind +import com.microsoft.agenthostprotocol.generated.SessionInputNumberQuestion +import com.microsoft.agenthostprotocol.generated.SessionInputQuestion +import com.microsoft.agenthostprotocol.generated.SessionInputQuestionNumber +import com.microsoft.agenthostprotocol.generated.SessionInputQuestionKind import com.microsoft.agenthostprotocol.generated.StringOrMarkdown import com.microsoft.agenthostprotocol.generated.ToolResultContent import kotlinx.serialization.json.JsonObject @@ -74,9 +74,9 @@ class DiscriminatedUnionTest { } @Test - fun `ChatInputQuestion accepts both number and integer wire kinds`() { + fun `SessionInputQuestion accepts both number and integer wire kinds`() { // Both "number" and "integer" wire values map to the same Kotlin - // data class (ChatInputNumberQuestion); the union serializer + // data class (SessionInputNumberQuestion); the union serializer // must dispatch on either. val numberWire = """{ "kind": "number", @@ -89,23 +89,23 @@ class DiscriminatedUnionTest { "message": "Pick an integer" }""".trimIndent() - val asNumber = json.decodeFromString(ChatInputQuestion.serializer(), numberWire) - val asInteger = json.decodeFromString(ChatInputQuestion.serializer(), integerWire) + val asNumber = json.decodeFromString(SessionInputQuestion.serializer(), numberWire) + val asInteger = json.decodeFromString(SessionInputQuestion.serializer(), integerWire) - val numberVariant = assertIs(asNumber) - val integerVariant = assertIs(asInteger) + val numberVariant = assertIs(asNumber) + val integerVariant = assertIs(asInteger) assertEquals("q1", numberVariant.value.id) assertEquals("q2", integerVariant.value.id) - assertEquals(ChatInputQuestionKind.NUMBER, numberVariant.value.kind) - assertEquals(ChatInputQuestionKind.INTEGER, integerVariant.value.kind) + assertEquals(SessionInputQuestionKind.NUMBER, numberVariant.value.kind) + assertEquals(SessionInputQuestionKind.INTEGER, integerVariant.value.kind) // Encode preserves whichever discriminator was originally set on // the data class. val reEncodedInteger = json.encodeToString( - ChatInputQuestion.serializer(), - ChatInputQuestionNumber( - ChatInputNumberQuestion( - kind = ChatInputQuestionKind.INTEGER, + SessionInputQuestion.serializer(), + SessionInputQuestionNumber( + SessionInputNumberQuestion( + kind = SessionInputQuestionKind.INTEGER, id = "q3", message = "Yet another integer", ), diff --git a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt index 37cf486b..229a69c0 100644 --- a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt +++ b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/FixtureDrivenReducerTest.kt @@ -1,6 +1,5 @@ package com.microsoft.agenthostprotocol -import com.microsoft.agenthostprotocol.generated.ChatState import com.microsoft.agenthostprotocol.generated.ChangesetState import com.microsoft.agenthostprotocol.generated.AnnotationsState import com.microsoft.agenthostprotocol.generated.ResourceWatchState @@ -52,8 +51,9 @@ class FixtureDrivenReducerTest { @BeforeEach fun mockTimestamp() { - // Match the TypeScript test mock (`Date.now = () => 9999`) so chat - // fixtures assert the corresponding ISO `modifiedAt` value. + // Match the TypeScript test mock (`Date.now = () => 9999`) so any + // fixture that asserts a `modifiedAt: 9999` field aligns with our + // reducer-produced output. originalProvider = currentTimestampProvider currentTimestampProvider = { MOCK_NOW } } @@ -156,18 +156,6 @@ class FixtureDrivenReducerTest { }, ) - "chat" -> compareFixture( - file = file, - initial = initial, - expected = expected, - serializer = ChatState.serializer(), - run = { state -> - var s = state - for (action in actions) s = chatReducer(s, action) - s - }, - ) - "terminal" -> compareFixture( file = file, initial = initial, diff --git a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/ReducersTest.kt b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/ReducersTest.kt index 92d93481..c8bbc154 100644 --- a/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/ReducersTest.kt +++ b/clients/kotlin/src/test/kotlin/com/microsoft/agenthostprotocol/ReducersTest.kt @@ -1,6 +1,46 @@ package com.microsoft.agenthostprotocol -import com.microsoft.agenthostprotocol.generated.* +import com.microsoft.agenthostprotocol.generated.AgentInfo +import com.microsoft.agenthostprotocol.generated.ChangesetFile +import com.microsoft.agenthostprotocol.generated.ChangesetState +import com.microsoft.agenthostprotocol.generated.ChangesetStatus +import com.microsoft.agenthostprotocol.generated.ChangesetStatusChangedAction +import com.microsoft.agenthostprotocol.generated.ActionType +import com.microsoft.agenthostprotocol.generated.ChangesetClearedAction +import com.microsoft.agenthostprotocol.generated.ChangesetFileSetAction +import com.microsoft.agenthostprotocol.generated.CustomizationUnknown +import com.microsoft.agenthostprotocol.generated.ErrorInfo +import com.microsoft.agenthostprotocol.generated.FileEdit +import com.microsoft.agenthostprotocol.generated.Message +import com.microsoft.agenthostprotocol.generated.PendingMessage +import com.microsoft.agenthostprotocol.generated.PendingMessageKind +import com.microsoft.agenthostprotocol.generated.RootAgentsChangedAction +import com.microsoft.agenthostprotocol.generated.RootState +import com.microsoft.agenthostprotocol.generated.SessionCustomizationUpdatedAction +import com.microsoft.agenthostprotocol.generated.SessionLifecycle +import com.microsoft.agenthostprotocol.generated.SessionPendingMessageSetAction +import com.microsoft.agenthostprotocol.generated.SessionQueuedMessagesReorderedAction +import com.microsoft.agenthostprotocol.generated.SessionState +import com.microsoft.agenthostprotocol.generated.SessionStatus +import com.microsoft.agenthostprotocol.generated.SessionSummary +import com.microsoft.agenthostprotocol.generated.SessionTitleChangedAction +import com.microsoft.agenthostprotocol.generated.StateActionChangesetCleared +import com.microsoft.agenthostprotocol.generated.StateActionChangesetFileSet +import com.microsoft.agenthostprotocol.generated.StateActionChangesetStatusChanged +import com.microsoft.agenthostprotocol.generated.StateActionRootAgentsChanged +import com.microsoft.agenthostprotocol.generated.StateActionSessionCustomizationUpdated +import com.microsoft.agenthostprotocol.generated.StateActionSessionPendingMessageSet +import com.microsoft.agenthostprotocol.generated.StateActionSessionQueuedMessagesReordered +import com.microsoft.agenthostprotocol.generated.StateActionSessionTitleChanged +import com.microsoft.agenthostprotocol.generated.StateActionTerminalData +import com.microsoft.agenthostprotocol.generated.StateActionTerminalInput +import com.microsoft.agenthostprotocol.generated.TerminalClientClaim +import com.microsoft.agenthostprotocol.generated.TerminalClaimClient +import com.microsoft.agenthostprotocol.generated.TerminalClaimKind +import com.microsoft.agenthostprotocol.generated.TerminalContentPartUnclassified +import com.microsoft.agenthostprotocol.generated.TerminalDataAction +import com.microsoft.agenthostprotocol.generated.TerminalInputAction +import com.microsoft.agenthostprotocol.generated.TerminalState import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject @@ -133,14 +173,14 @@ class ReducersTest { PendingMessage(id = "m2", message = userMessage("2")), PendingMessage(id = "m3", message = userMessage("3")), ) - val chat = newChat().copy(queuedMessages = original) - val reorder = StateActionChatQueuedMessagesReordered( - ChatQueuedMessagesReorderedAction( - type = ActionType.CHAT_QUEUED_MESSAGES_REORDERED, + val session = newSession().copy(queuedMessages = original) + val reorder = StateActionSessionQueuedMessagesReordered( + SessionQueuedMessagesReorderedAction( + type = ActionType.SESSION_QUEUED_MESSAGES_REORDERED, order = listOf("m3", "m1"), ), ) - val result = chatReducer(chat, reorder) + val result = sessionReducer(session, reorder) assertEquals(listOf("m3", "m1", "m2"), result.queuedMessages?.map { it.id }) } @@ -150,61 +190,61 @@ class ReducersTest { PendingMessage(id = "m1", message = userMessage("1")), PendingMessage(id = "m2", message = userMessage("2")), ) - val chat = newChat().copy(queuedMessages = original) - val reorder = StateActionChatQueuedMessagesReordered( - ChatQueuedMessagesReorderedAction( - type = ActionType.CHAT_QUEUED_MESSAGES_REORDERED, + val session = newSession().copy(queuedMessages = original) + val reorder = StateActionSessionQueuedMessagesReordered( + SessionQueuedMessagesReorderedAction( + type = ActionType.SESSION_QUEUED_MESSAGES_REORDERED, order = listOf("m2", "m999", "m2", "m1"), ), ) - val result = chatReducer(chat, reorder) + val result = sessionReducer(session, reorder) assertEquals(listOf("m2", "m1"), result.queuedMessages?.map { it.id }) } @Test fun `pendingMessageSet upserts steering and queued messages distinctly`() { - val chat = newChat() - val setSteering = StateActionChatPendingMessageSet( - ChatPendingMessageSetAction( - type = ActionType.CHAT_PENDING_MESSAGE_SET, + val session = newSession() + val setSteering = StateActionSessionPendingMessageSet( + SessionPendingMessageSetAction( + type = ActionType.SESSION_PENDING_MESSAGE_SET, kind = PendingMessageKind.STEERING, id = "s1", message = userMessage("steer"), ), ) - val withSteering = chatReducer(chat, setSteering) + val withSteering = sessionReducer(session, setSteering) assertEquals("s1", withSteering.steeringMessage?.id) assertNull(withSteering.queuedMessages) - val setQueued1 = StateActionChatPendingMessageSet( - ChatPendingMessageSetAction( - type = ActionType.CHAT_PENDING_MESSAGE_SET, + val setQueued1 = StateActionSessionPendingMessageSet( + SessionPendingMessageSetAction( + type = ActionType.SESSION_PENDING_MESSAGE_SET, kind = PendingMessageKind.QUEUED, id = "q1", message = userMessage("q-1"), ), ) - val setQueued2 = StateActionChatPendingMessageSet( - ChatPendingMessageSetAction( - type = ActionType.CHAT_PENDING_MESSAGE_SET, + val setQueued2 = StateActionSessionPendingMessageSet( + SessionPendingMessageSetAction( + type = ActionType.SESSION_PENDING_MESSAGE_SET, kind = PendingMessageKind.QUEUED, id = "q2", message = userMessage("q-2"), ), ) - val withTwo = chatReducer(chatReducer(withSteering, setQueued1), setQueued2) + val withTwo = sessionReducer(sessionReducer(withSteering, setQueued1), setQueued2) assertEquals(listOf("q1", "q2"), withTwo.queuedMessages?.map { it.id }) // Re-setting q1 with a new body should replace in place rather than append. - val replaceQueued1 = StateActionChatPendingMessageSet( - ChatPendingMessageSetAction( - type = ActionType.CHAT_PENDING_MESSAGE_SET, + val replaceQueued1 = StateActionSessionPendingMessageSet( + SessionPendingMessageSetAction( + type = ActionType.SESSION_PENDING_MESSAGE_SET, kind = PendingMessageKind.QUEUED, id = "q1", message = userMessage("q-1-revised"), ), ) - val withReplacement = chatReducer(withTwo, replaceQueued1) + val withReplacement = sessionReducer(withTwo, replaceQueued1) assertEquals(listOf("q1", "q2"), withReplacement.queuedMessages?.map { it.id }) assertEquals("q-1-revised", withReplacement.queuedMessages?.first()?.message?.text) } @@ -218,18 +258,6 @@ class ReducersTest { ) val result = sessionReducer(session, titleAction) assertEquals(12345L, result.summary.modifiedAt) - - val chatResult = chatReducer( - newChat(), - StateActionChatTurnStarted( - ChatTurnStartedAction( - type = ActionType.CHAT_TURN_STARTED, - turnId = "turn-1", - message = userMessage("hello"), - ), - ), - ) - assertEquals("1970-01-01T00:00:12.345Z", chatResult.modifiedAt) } @Test @@ -294,7 +322,7 @@ class ReducersTest { private fun newSession(): SessionState = SessionState( summary = SessionSummary( - resource = "ahp-session:/test", + resource = "copilot:/test", provider = "copilot", title = "Test", status = SessionStatus.IDLE, @@ -302,14 +330,6 @@ class ReducersTest { modifiedAt = 1000L, ), lifecycle = SessionLifecycle.READY, - chats = emptyList(), - ) - - private fun newChat(): ChatState = ChatState( - resource = "ahp-chat:/test/default", - title = "Test", - status = SessionStatus.IDLE, - modifiedAt = "1970-01-01T00:00:01Z", turns = emptyList(), ) diff --git a/clients/rust/CHANGELOG.md b/clients/rust/CHANGELOG.md index 734f1086..120512d0 100644 --- a/clients/rust/CHANGELOG.md +++ b/clients/rust/CHANGELOG.md @@ -36,22 +36,12 @@ matching `## [X.Y.Z]` heading is missing from this file. resending its entries. Handled by the annotations reducer (no-op on unknown id). -- `ahp-chat:` channel for per-chat conversation state; `SessionState.chats[]` catalog; `SessionState.defaultChat?` input-routing hint; `ChatOrigin` provenance union; `createChat` / `disposeChat` commands. -- `ChatSummary.working_directory` — optional per-chat working directory. Falls back to the session's `working_directory` when absent. -- Three discrete chat-catalog actions on the session channel — `SessionChatAdded` (upsert by `summary.resource`), `SessionChatRemoved`, and `SessionChatUpdated` (partial-update payload). +### Added + - `RootState` now exposes an optional `_meta` property bag (`meta: Option`) for implementation-defined agent-host metadata, such as a well-known `hostBuild` key carrying the host's build version/commit/date. -### Changed - -- `ChatState` is now flat — the previous embedded `summary` has been replaced with inlined `resource` / `title` / `status` / `activity` / `modified_at` / `model` / `agent` / `origin` / `working_directory` fields. `ChatSummary` remains as the standalone catalog entry on `SessionState.chats`. -- `ChatSummary.modified_at` and `ChatState.modified_at` are now ISO 8601 `String` values instead of `u64` milliseconds. - -### Removed - -- `SessionChatsChanged` variant on `StateAction` (replaced by the three discrete chat-catalog variants above). - ## [0.3.0] — 2026-06-05 Implements AHP 0.3.0. @@ -97,14 +87,11 @@ Implements AHP 0.3.0. ### Changed -- `fetchTurns` and `completions` now target an `ahp-chat:` channel; `PROTOCOL_VERSION` bumped to `0.4.0`. -- Reducers split into per-chat and session-aggregate handlers to match the multi-chat protocol shape. `SessionInput*` types renamed to `ChatInput*` (they now live on the chat channel). - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. - Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed -- `SessionState.turns`, `SessionState.activeTurn`, `SessionState.steeringMessage`, `SessionState.queuedMessages`, `SessionState.inputRequests` (moved to `ChatState`). - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. ### Changed diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs index 9d61e65b..6e34003d 100644 --- a/clients/rust/crates/ahp-types/src/actions.rs +++ b/clients/rust/crates/ahp-types/src/actions.rs @@ -13,12 +13,12 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::state::{ AgentInfo, AgentSelection, Annotation, AnnotationEntry, Changeset, ChangesetFile, - ChangesetOperation, ChangesetOperationStatus, ChangesetStatus, ChatInputAnswer, - ChatInputRequest, ChatInputResponseKind, ChatOrigin, ChatSummary, ConfirmationOption, + ChangesetOperation, ChangesetOperationStatus, ChangesetStatus, ConfirmationOption, Customization, ErrorInfo, McpServerState, Message, ModelSelection, PendingMessageKind, - ResponsePart, SessionActiveClient, TerminalClaim, TerminalInfo, TextRange, - ToolCallCancellationReason, ToolCallConfirmationReason, ToolCallContributor, ToolCallResult, - ToolDefinition, ToolResultContent, UsageInfo, + ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, + SessionInputResponseKind, TerminalClaim, TerminalInfo, TextRange, ToolCallCancellationReason, + ToolCallConfirmationReason, ToolCallContributor, ToolCallResult, ToolDefinition, + ToolResultContent, UsageInfo, }; // ─── ActionType ────────────────────────────────────────────────────── @@ -34,46 +34,38 @@ pub enum ActionType { 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/turnComplete")] - ChatTurnComplete, - #[serde(rename = "chat/turnCancelled")] - ChatTurnCancelled, - #[serde(rename = "chat/error")] - ChatError, + #[serde(rename = "session/turnStarted")] + SessionTurnStarted, + #[serde(rename = "session/delta")] + SessionDelta, + #[serde(rename = "session/responsePart")] + SessionResponsePart, + #[serde(rename = "session/toolCallStart")] + SessionToolCallStart, + #[serde(rename = "session/toolCallDelta")] + SessionToolCallDelta, + #[serde(rename = "session/toolCallReady")] + SessionToolCallReady, + #[serde(rename = "session/toolCallConfirmed")] + SessionToolCallConfirmed, + #[serde(rename = "session/toolCallComplete")] + SessionToolCallComplete, + #[serde(rename = "session/toolCallResultConfirmed")] + SessionToolCallResultConfirmed, + #[serde(rename = "session/toolCallContentChanged")] + SessionToolCallContentChanged, + #[serde(rename = "session/turnComplete")] + SessionTurnComplete, + #[serde(rename = "session/turnCancelled")] + SessionTurnCancelled, + #[serde(rename = "session/error")] + SessionError, #[serde(rename = "session/titleChanged")] SessionTitleChanged, - #[serde(rename = "chat/usage")] - ChatUsage, - #[serde(rename = "chat/reasoning")] - ChatReasoning, + #[serde(rename = "session/usage")] + SessionUsage, + #[serde(rename = "session/reasoning")] + SessionReasoning, #[serde(rename = "session/modelChanged")] SessionModelChanged, #[serde(rename = "session/agentChanged")] @@ -84,18 +76,18 @@ pub enum ActionType { SessionActiveClientChanged, #[serde(rename = "session/activeClientToolsChanged")] SessionActiveClientToolsChanged, - #[serde(rename = "chat/pendingMessageSet")] - ChatPendingMessageSet, - #[serde(rename = "chat/pendingMessageRemoved")] - ChatPendingMessageRemoved, - #[serde(rename = "chat/queuedMessagesReordered")] - ChatQueuedMessagesReordered, - #[serde(rename = "chat/inputRequested")] - ChatInputRequested, - #[serde(rename = "chat/inputAnswerChanged")] - ChatInputAnswerChanged, - #[serde(rename = "chat/inputCompleted")] - ChatInputCompleted, + #[serde(rename = "session/pendingMessageSet")] + SessionPendingMessageSet, + #[serde(rename = "session/pendingMessageRemoved")] + SessionPendingMessageRemoved, + #[serde(rename = "session/queuedMessagesReordered")] + SessionQueuedMessagesReordered, + #[serde(rename = "session/inputRequested")] + SessionInputRequested, + #[serde(rename = "session/inputAnswerChanged")] + SessionInputAnswerChanged, + #[serde(rename = "session/inputCompleted")] + SessionInputCompleted, #[serde(rename = "session/customizationsChanged")] SessionCustomizationsChanged, #[serde(rename = "session/customizationToggled")] @@ -106,8 +98,8 @@ pub enum ActionType { SessionCustomizationRemoved, #[serde(rename = "session/mcpServerStateChanged")] SessionMcpServerStateChanged, - #[serde(rename = "chat/truncated")] - ChatTruncated, + #[serde(rename = "session/truncated")] + SessionTruncated, #[serde(rename = "session/isReadChanged")] SessionIsReadChanged, #[serde(rename = "session/isArchivedChanged")] @@ -246,63 +238,12 @@ pub struct SessionCreationFailedAction { pub error: ErrorInfo, } -/// A chat was added to this session's catalog. Upsert semantics: if a chat -/// with the same `summary.resource` already exists, the existing entry is -/// replaced. -/// -/// Mirrors the root-channel `root/sessionAdded` notification. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionChatAddedAction { - /// The full summary of the newly added (or upserted) chat. - pub summary: ChatSummary, -} - -/// A chat was removed from this session's catalog. No-op when no entry matches. -/// -/// Mirrors the root-channel `root/sessionRemoved` notification. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionChatRemovedAction { - /// The URI of the chat to remove. - pub chat: Uri, -} - -/// One existing chat's summary fields changed. -/// -/// Partial-update semantics: only fields present in `changes` are written; -/// omitted fields are preserved. Identity fields (`resource`) MUST NOT be -/// carried in `changes`. No-op when no entry with `chat` exists — clients -/// SHOULD then wait for a {@link SessionChatAddedAction | `session/chatAdded`}. -/// -/// Mirrors the root-channel `root/sessionSummaryChanged` notification. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SessionChatUpdatedAction { - /// The URI of the chat whose summary changed. - pub chat: Uri, - /// Mutable summary fields that changed; omitted fields are unchanged. - /// - /// Identity fields (`resource`) never change and MUST be omitted by - /// senders; receivers SHOULD ignore them if present. - pub changes: PartialChatSummary, -} - -/// The default chat input-routing hint for this session changed. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct SessionDefaultChatChangedAction { - /// New default chat URI, or `undefined` to clear the hint. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub default_chat: Option, -} - /// A new message has been sent to the agent, and a new turn starts. /// /// A client is only allowed to send {@link MessageKind.User} messages. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatTurnStartedAction { +pub struct SessionTurnStartedAction { /// Turn identifier pub turn_id: String, /// The new message @@ -314,11 +255,11 @@ pub struct ChatTurnStartedAction { /// Streaming text chunk from the assistant, appended to a specific response part. /// -/// The server MUST first emit a `chat/responsePart` to create the target +/// The server MUST first emit a `session/responsePart` to create the target /// part (markdown or reasoning), then use this action to append text to it. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatDeltaAction { +pub struct SessionDeltaAction { /// Turn identifier pub turn_id: String, /// Identifier of the response part to append to @@ -330,7 +271,7 @@ pub struct ChatDeltaAction { /// Structured content appended to the response. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatResponsePartAction { +pub struct SessionResponsePartAction { /// Turn identifier pub turn_id: String, /// Response part (markdown or content ref) @@ -342,11 +283,11 @@ pub struct ChatResponsePartAction { /// The server sets {@link ToolCallContributor | `contributor`} to identify /// the origin of the tool. For client-provided tools, the named client is /// responsible for executing the tool once it reaches the `running` state -/// and dispatching `chat/toolCallComplete`. For MCP-served tools, the +/// and dispatching `session/toolCallComplete`. For MCP-served tools, the /// server executes the call against the named `McpServerCustomization`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatToolCallStartAction { +pub struct SessionToolCallStartAction { /// Turn identifier pub turn_id: String, /// Tool call identifier @@ -372,7 +313,7 @@ pub struct ChatToolCallStartAction { /// Streaming partial parameters for a tool call. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatToolCallDeltaAction { +pub struct SessionToolCallDeltaAction { /// Turn identifier pub turn_id: String, /// Tool call identifier @@ -400,14 +341,14 @@ pub struct ChatToolCallDeltaAction { /// When dispatched for a `running` tool call (e.g. mid-execution permission needed), /// transitions back to `pending-confirmation`. The `invocationMessage` and `_meta` /// SHOULD be updated to describe the specific confirmation needed. Clients use the -/// standard `chat/toolCallConfirmed` flow to approve or deny. +/// standard `session/toolCallConfirmed` flow to approve or deny. /// /// For client-provided tools, the server typically sets `confirmed` to /// `'not-needed'` so the tool transitions directly to `running`, where the /// owning client can begin execution immediately. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatToolCallReadyAction { +pub struct SessionToolCallReadyAction { /// Turn identifier pub turn_id: String, /// Tool call identifier @@ -448,7 +389,7 @@ pub struct ChatToolCallReadyAction { /// Client approves or denies a pending tool call (merged approved + denied variants). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatToolCallConfirmedAction { +pub struct SessionToolCallConfirmedAction { pub turn_id: String, pub tool_call_id: String, /// Additional provider-specific metadata for this tool call. @@ -488,7 +429,7 @@ pub struct ChatToolCallConfirmedAction { /// this action with `result.success = false` and an appropriate error. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatToolCallCompleteAction { +pub struct SessionToolCallCompleteAction { /// Turn identifier pub turn_id: String, /// Tool call identifier @@ -513,7 +454,7 @@ pub struct ChatToolCallCompleteAction { /// If `approved` is `false`, the tool transitions to `cancelled` with reason `result-denied`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatToolCallResultConfirmedAction { +pub struct SessionToolCallResultConfirmedAction { /// Turn identifier pub turn_id: String, /// Tool call identifier @@ -530,39 +471,10 @@ pub struct ChatToolCallResultConfirmedAction { pub approved: bool, } -/// Partial content produced while a tool is still executing. -/// -/// Replaces the `content` array on the running tool call state. Clients can -/// use this to display live feedback (e.g. a terminal reference) before the -/// tool completes. -/// -/// For client-provided tools (where `toolClientId` is set on the tool call state), -/// the owning client dispatches this action to stream intermediate content while -/// executing. The server SHOULD reject this action if the dispatching client does -/// not match `toolClientId`. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatToolCallContentChangedAction { - /// Turn identifier - pub turn_id: String, - /// Tool call identifier - pub tool_call_id: String, - /// Additional provider-specific metadata for this tool call. - /// - /// Clients MAY look for well-known keys here to provide enhanced UI. - /// For example, a `ptyTerminal` key with `{ input: string; output: string }` - /// indicates the tool operated on a terminal (both `input` and `output` may - /// contain escape sequences). - #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] - pub meta: Option, - /// The current partial content for the running tool call - pub content: Vec, -} - /// Turn finished — the assistant is idle. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatTurnCompleteAction { +pub struct SessionTurnCompleteAction { /// Turn identifier pub turn_id: String, } @@ -570,7 +482,7 @@ pub struct ChatTurnCompleteAction { /// Turn was aborted; server stops processing. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatTurnCancelledAction { +pub struct SessionTurnCancelledAction { /// Turn identifier pub turn_id: String, } @@ -578,7 +490,7 @@ pub struct ChatTurnCancelledAction { /// Error during turn processing. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatErrorAction { +pub struct SessionErrorAction { /// Turn identifier pub turn_id: String, /// Error details @@ -597,7 +509,7 @@ pub struct SessionTitleChangedAction { /// Token usage report for a turn. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatUsageAction { +pub struct SessionUsageAction { /// Turn identifier pub turn_id: String, /// Token usage data @@ -606,11 +518,11 @@ pub struct ChatUsageAction { /// Reasoning/thinking text from the model, appended to a specific reasoning response part. /// -/// The server MUST first emit a `chat/responsePart` to create the target +/// The server MUST first emit a `session/responsePart` to create the target /// reasoning part, then use this action to append text to it. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatReasoningAction { +pub struct SessionReasoningAction { /// Turn identifier pub turn_id: String, /// Identifier of the reasoning response part to append to @@ -736,14 +648,14 @@ pub struct SessionActiveClientToolsChangedAction { /// /// For steering messages, this always replaces the single steering message. /// For queued messages, if a message with the given `id` already exists it is -/// updated in place; otherwise it is appended to the queue. If the chat is +/// updated in place; otherwise it is appended to the queue. If the session is /// idle when a queued message is set, the server SHOULD immediately consume it /// and start a new turn. /// /// A client is only allowed to send {@link MessageKind.User} messages. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatPendingMessageSetAction { +pub struct SessionPendingMessageSetAction { /// Whether this is a steering or queued message pub kind: PendingMessageKind, /// Unique identifier for this pending message @@ -759,7 +671,7 @@ pub struct ChatPendingMessageSetAction { /// injecting a steering message into the current turn). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatPendingMessageRemovedAction { +pub struct SessionPendingMessageRemovedAction { /// Whether this is a steering or queued message pub kind: PendingMessageKind, /// Identifier of the pending message to remove @@ -775,7 +687,7 @@ pub struct ChatPendingMessageRemovedAction { /// view of the queue never silently drops messages). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatQueuedMessagesReorderedAction { +pub struct SessionQueuedMessagesReorderedAction { /// Queued message IDs in the desired order pub order: Vec, } @@ -787,9 +699,9 @@ pub struct ChatQueuedMessagesReorderedAction { /// unless `request.answers` is provided. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputRequestedAction { +pub struct SessionInputRequestedAction { /// Input request to create or replace - pub request: ChatInputRequest, + pub request: SessionInputRequest, } /// A client updated, submitted, skipped, or removed a single in-progress answer. @@ -797,14 +709,14 @@ pub struct ChatInputRequestedAction { /// Dispatching with `answer: undefined` removes that question's answer draft. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputAnswerChangedAction { +pub struct SessionInputAnswerChangedAction { /// Input request identifier pub request_id: String, /// Question identifier within the input request pub question_id: String, /// Updated answer, or `undefined` to clear an answer draft #[serde(default, skip_serializing_if = "Option::is_none")] - pub answer: Option, + pub answer: Option, } /// A client accepted, declined, or cancelled a session input request. @@ -813,14 +725,14 @@ pub struct ChatInputAnswerChangedAction { /// synced answer state to resume the blocked operation. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputCompletedAction { +pub struct SessionInputCompletedAction { /// Input request identifier pub request_id: String, /// Completion outcome - pub response: ChatInputResponseKind, + pub response: SessionInputResponseKind, /// Optional final answer replacement, keyed by question ID #[serde(default, skip_serializing_if = "Option::is_none")] - pub answers: Option>, + pub answers: Option>, } /// The session's customizations have changed. @@ -914,14 +826,14 @@ pub struct SessionMcpServerStateChangedAction { /// turn are removed and the specified turn is kept. If `turnId` is omitted, all /// turns are removed. /// -/// If there is an active turn it is silently dropped and the chat status +/// If there is an active turn it is silently dropped and the session status /// returns to `idle`. /// /// Common use-case: truncate old data then dispatch a new -/// `chat/turnStarted` with an edited message. +/// `session/turnStarted` with an edited message. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] -pub struct ChatTruncatedAction { +pub struct SessionTruncatedAction { /// Keep turns up to and including this turn. Omit to clear all turns. #[serde(default, skip_serializing_if = "Option::is_none")] pub turn_id: Option, @@ -953,6 +865,35 @@ pub struct SessionMetaChangedAction { pub meta: Option, } +/// Partial content produced while a tool is still executing. +/// +/// Replaces the `content` array on the running tool call state. Clients can +/// use this to display live feedback (e.g. a terminal reference) before the +/// tool completes. +/// +/// For client-provided tools (where `toolClientId` is set on the tool call state), +/// the owning client dispatches this action to stream intermediate content while +/// executing. The server SHOULD reject this action if the dispatching client does +/// not match `toolClientId`. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionToolCallContentChangedAction { + /// Turn identifier + pub turn_id: String, + /// Tool call identifier + pub tool_call_id: String, + /// Additional provider-specific metadata for this tool call. + /// + /// Clients MAY look for well-known keys here to provide enhanced UI. + /// For example, a `ptyTerminal` key with `{ input: string; output: string }` + /// indicates the tool operated on a terminal (both `input` and `output` may + /// contain escape sequences). + #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] + pub meta: Option, + /// The current partial content for the running tool call + pub content: Vec, +} + /// The {@link ChangesetState.status} for this changeset transitioned (e.g. /// `computing → ready`). The error payload is set together with `status` /// whenever it transitions to {@link ChangesetStatus.Error | Error}. @@ -1299,45 +1240,6 @@ pub struct ResourceWatchChangedAction { pub changes: AnyValue, } -// ─── Partial Summaries ──────────────────────────────────────────────── - -/// Partial equivalent of ChatSummary — every field is optional for delta updates. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct PartialChatSummary { - /// Chat URI - #[serde(default, skip_serializing_if = "Option::is_none")] - pub resource: Option, - /// Chat title - #[serde(default, skip_serializing_if = "Option::is_none")] - pub title: Option, - /// Current chat status (reuses SessionStatus shape) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub status: Option, - /// Human-readable description of what the chat is currently doing - #[serde(default, skip_serializing_if = "Option::is_none")] - pub activity: Option, - /// Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub modified_at: Option, - /// Optional per-chat model override (defaults to the session's model) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Optional per-chat agent override (defaults to the session's agent) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub agent: Option, - /// How this chat came into existence - #[serde(default, skip_serializing_if = "Option::is_none")] - pub origin: Option, - /// Optional per-chat working directory. - /// - /// If absent, the chat inherits - /// {@link SessionSummary.workingDirectory | the session's working directory}. - /// See {@link ChatState.workingDirectory} for usage notes. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub working_directory: Option, -} - // ─── StateAction Union ─────────────────────────────────────────────── /// Discriminated union of every state action. @@ -1354,46 +1256,36 @@ pub enum StateAction { SessionReady(SessionReadyAction), #[serde(rename = "session/creationFailed")] SessionCreationFailed(SessionCreationFailedAction), - #[serde(rename = "session/chatAdded")] - SessionChatAdded(SessionChatAddedAction), - #[serde(rename = "session/chatRemoved")] - SessionChatRemoved(SessionChatRemovedAction), - #[serde(rename = "session/chatUpdated")] - SessionChatUpdated(SessionChatUpdatedAction), - #[serde(rename = "session/defaultChatChanged")] - SessionDefaultChatChanged(SessionDefaultChatChangedAction), - #[serde(rename = "chat/turnStarted")] - ChatTurnStarted(ChatTurnStartedAction), - #[serde(rename = "chat/delta")] - ChatDelta(ChatDeltaAction), - #[serde(rename = "chat/responsePart")] - ChatResponsePart(ChatResponsePartAction), - #[serde(rename = "chat/toolCallStart")] - ChatToolCallStart(ChatToolCallStartAction), - #[serde(rename = "chat/toolCallDelta")] - ChatToolCallDelta(ChatToolCallDeltaAction), - #[serde(rename = "chat/toolCallReady")] - ChatToolCallReady(ChatToolCallReadyAction), - #[serde(rename = "chat/toolCallConfirmed")] - ChatToolCallConfirmed(ChatToolCallConfirmedAction), - #[serde(rename = "chat/toolCallComplete")] - ChatToolCallComplete(ChatToolCallCompleteAction), - #[serde(rename = "chat/toolCallResultConfirmed")] - ChatToolCallResultConfirmed(ChatToolCallResultConfirmedAction), - #[serde(rename = "chat/toolCallContentChanged")] - ChatToolCallContentChanged(ChatToolCallContentChangedAction), - #[serde(rename = "chat/turnComplete")] - ChatTurnComplete(ChatTurnCompleteAction), - #[serde(rename = "chat/turnCancelled")] - ChatTurnCancelled(ChatTurnCancelledAction), - #[serde(rename = "chat/error")] - ChatError(ChatErrorAction), + #[serde(rename = "session/turnStarted")] + SessionTurnStarted(SessionTurnStartedAction), + #[serde(rename = "session/delta")] + SessionDelta(SessionDeltaAction), + #[serde(rename = "session/responsePart")] + SessionResponsePart(SessionResponsePartAction), + #[serde(rename = "session/toolCallStart")] + SessionToolCallStart(SessionToolCallStartAction), + #[serde(rename = "session/toolCallDelta")] + SessionToolCallDelta(SessionToolCallDeltaAction), + #[serde(rename = "session/toolCallReady")] + SessionToolCallReady(SessionToolCallReadyAction), + #[serde(rename = "session/toolCallConfirmed")] + SessionToolCallConfirmed(SessionToolCallConfirmedAction), + #[serde(rename = "session/toolCallComplete")] + SessionToolCallComplete(SessionToolCallCompleteAction), + #[serde(rename = "session/toolCallResultConfirmed")] + SessionToolCallResultConfirmed(SessionToolCallResultConfirmedAction), + #[serde(rename = "session/turnComplete")] + SessionTurnComplete(SessionTurnCompleteAction), + #[serde(rename = "session/turnCancelled")] + SessionTurnCancelled(SessionTurnCancelledAction), + #[serde(rename = "session/error")] + SessionError(SessionErrorAction), #[serde(rename = "session/titleChanged")] SessionTitleChanged(SessionTitleChangedAction), - #[serde(rename = "chat/usage")] - ChatUsage(ChatUsageAction), - #[serde(rename = "chat/reasoning")] - ChatReasoning(ChatReasoningAction), + #[serde(rename = "session/usage")] + SessionUsage(SessionUsageAction), + #[serde(rename = "session/reasoning")] + SessionReasoning(SessionReasoningAction), #[serde(rename = "session/modelChanged")] SessionModelChanged(SessionModelChangedAction), #[serde(rename = "session/agentChanged")] @@ -1412,18 +1304,18 @@ pub enum StateAction { SessionActiveClientChanged(SessionActiveClientChangedAction), #[serde(rename = "session/activeClientToolsChanged")] SessionActiveClientToolsChanged(SessionActiveClientToolsChangedAction), - #[serde(rename = "chat/pendingMessageSet")] - ChatPendingMessageSet(ChatPendingMessageSetAction), - #[serde(rename = "chat/pendingMessageRemoved")] - ChatPendingMessageRemoved(ChatPendingMessageRemovedAction), - #[serde(rename = "chat/queuedMessagesReordered")] - ChatQueuedMessagesReordered(ChatQueuedMessagesReorderedAction), - #[serde(rename = "chat/inputRequested")] - ChatInputRequested(ChatInputRequestedAction), - #[serde(rename = "chat/inputAnswerChanged")] - ChatInputAnswerChanged(ChatInputAnswerChangedAction), - #[serde(rename = "chat/inputCompleted")] - ChatInputCompleted(ChatInputCompletedAction), + #[serde(rename = "session/pendingMessageSet")] + SessionPendingMessageSet(SessionPendingMessageSetAction), + #[serde(rename = "session/pendingMessageRemoved")] + SessionPendingMessageRemoved(SessionPendingMessageRemovedAction), + #[serde(rename = "session/queuedMessagesReordered")] + SessionQueuedMessagesReordered(SessionQueuedMessagesReorderedAction), + #[serde(rename = "session/inputRequested")] + SessionInputRequested(SessionInputRequestedAction), + #[serde(rename = "session/inputAnswerChanged")] + SessionInputAnswerChanged(SessionInputAnswerChangedAction), + #[serde(rename = "session/inputCompleted")] + SessionInputCompleted(SessionInputCompletedAction), #[serde(rename = "session/customizationsChanged")] SessionCustomizationsChanged(SessionCustomizationsChangedAction), #[serde(rename = "session/customizationToggled")] @@ -1434,12 +1326,14 @@ pub enum StateAction { SessionCustomizationRemoved(SessionCustomizationRemovedAction), #[serde(rename = "session/mcpServerStateChanged")] SessionMcpServerStateChanged(Box), - #[serde(rename = "chat/truncated")] - ChatTruncated(ChatTruncatedAction), + #[serde(rename = "session/truncated")] + SessionTruncated(SessionTruncatedAction), #[serde(rename = "session/configChanged")] SessionConfigChanged(SessionConfigChangedAction), #[serde(rename = "session/metaChanged")] SessionMetaChanged(SessionMetaChangedAction), + #[serde(rename = "session/toolCallContentChanged")] + SessionToolCallContentChanged(SessionToolCallContentChangedAction), #[serde(rename = "changeset/statusChanged")] ChangesetStatusChanged(ChangesetStatusChangedAction), #[serde(rename = "changeset/fileSet")] diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs index 53bde24f..a87e1626 100644 --- a/clients/rust/crates/ahp-types/src/commands.rs +++ b/clients/rust/crates/ahp-types/src/commands.rs @@ -15,7 +15,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use crate::actions::{ActionEnvelope, StateAction}; #[allow(unused_imports)] use crate::state::{ - AgentSelection, ContentRef, Message, MessageAttachment, ModelSelection, SessionActiveClient, + AgentSelection, ContentRef, MessageAttachment, ModelSelection, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn, }; @@ -312,46 +312,6 @@ pub struct DisposeSessionParams { pub channel: Uri, } -/// Identifies a source chat and turn to fork from. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatForkSource { - /// URI of the existing chat to fork from - pub chat: Uri, - /// Turn ID in the source chat; content up to and including this turn's response is copied - pub turn_id: String, -} - -/// Creates a new chat within a session. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CreateChatParams { - /// Channel URI this command targets. - pub channel: Uri, - /// Chat URI (client-chosen, e.g. `ahp-chat:/`). - pub chat: Uri, - /// Optional initial message for the new chat. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub initial_message: Option, - /// Optional per-chat model override. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Optional per-chat agent override. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub agent: Option, - /// Optional source chat and turn to fork from. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub source: Option, -} - -/// Disposes a chat and cleans up server-side resources. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DisposeChatParams { - /// Channel URI this command targets. - pub channel: Uri, -} - /// Returns a list of session summaries. Used to populate session lists and sidebars. /// /// The session list is **not** part of the state tree because it can be arbitrarily @@ -770,7 +730,7 @@ pub struct CreateResourceWatchResult { pub channel: Uri, } -/// Fetches historical turns for a chat. Used for lazy loading of conversation +/// Fetches historical turns for a session. Used for lazy loading of conversation /// history. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/clients/rust/crates/ahp-types/src/notifications.rs b/clients/rust/crates/ahp-types/src/notifications.rs index 5cc5f2d9..826f3bef 100644 --- a/clients/rust/crates/ahp-types/src/notifications.rs +++ b/clients/rust/crates/ahp-types/src/notifications.rs @@ -214,10 +214,7 @@ pub struct PartialSessionSummary { /// — the session uses the provider's default behavior. #[serde(default, skip_serializing_if = "Option::is_none")] pub agent: Option, - /// The default working directory URI for this session. Individual chats - /// MAY override via {@link ChatSummary.workingDirectory | their own - /// `workingDirectory`}; this field acts as the fallback for any chat that - /// does not. + /// The working directory URI for this session #[serde(default, skip_serializing_if = "Option::is_none")] pub working_directory: Option, /// Aggregate summary of file changes associated with this session. Servers diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 87e46501..5e8677e7 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -68,19 +68,9 @@ pub enum SessionStatus { IsArchived = 64, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum ChatOriginKind { - #[serde(rename = "user")] - User, - #[serde(rename = "fork")] - Fork, - #[serde(rename = "tool")] - Tool, -} - /// Answer lifecycle state. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum ChatInputAnswerState { +pub enum SessionInputAnswerState { #[serde(rename = "draft")] Draft, #[serde(rename = "submitted")] @@ -91,7 +81,7 @@ pub enum ChatInputAnswerState { /// Answer value kind. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum ChatInputAnswerValueKind { +pub enum SessionInputAnswerValueKind { #[serde(rename = "text")] Text, #[serde(rename = "number")] @@ -106,7 +96,7 @@ pub enum ChatInputAnswerValueKind { /// Question/input control kind. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum ChatInputQuestionKind { +pub enum SessionInputQuestionKind { #[serde(rename = "text")] Text, #[serde(rename = "number")] @@ -123,7 +113,7 @@ pub enum ChatInputQuestionKind { /// How a client completed an input request. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum ChatInputResponseKind { +pub enum SessionInputResponseKind { #[serde(rename = "accept")] Accept, #[serde(rename = "decline")] @@ -754,103 +744,6 @@ pub struct PendingMessage { pub message: Message, } -/// Full state for a single chat, loaded when a client subscribes to the chat's -/// URI. -/// -/// The lightweight catalog representation of a chat is {@link ChatSummary}, -/// carried in {@link SessionState.chats | `SessionState.chats`}. `ChatState` -/// **denormalizes** every {@link ChatSummary} field directly onto itself so -/// subscribers receive one flat object instead of having to merge a nested -/// `summary` sub-object. Producers MUST keep the two representations -/// consistent: any change to the inlined fields below SHOULD also be -/// announced on the parent session via the matching -/// {@link SessionChatUpdatedAction | `session/chatUpdated`} action. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatState { - /// Chat URI - pub resource: Uri, - /// Chat title - pub title: String, - /// Current chat status (reuses SessionStatus shape) - pub status: u32, - /// Human-readable description of what the chat is currently doing - #[serde(default, skip_serializing_if = "Option::is_none")] - pub activity: Option, - /// Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - pub modified_at: String, - /// Optional per-chat model override (defaults to the session's model) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Optional per-chat agent override (defaults to the session's agent) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub agent: Option, - /// How this chat came into existence - #[serde(default, skip_serializing_if = "Option::is_none")] - pub origin: Option, - /// Optional per-chat working directory. - /// - /// If absent, the chat inherits - /// {@link SessionSummary.workingDirectory | the session's working directory}. - /// Hosts MAY override this for individual chats — for example, to give a - /// subordinate chat its own git worktree so multiple chats in a session can - /// make independent edits that the orchestrator later merges back. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub working_directory: Option, - /// Completed turns - pub turns: Vec, - /// Currently in-progress turn - #[serde(default, skip_serializing_if = "Option::is_none")] - pub active_turn: Option, - /// Message to inject into the current turn at a convenient point - #[serde(default, skip_serializing_if = "Option::is_none")] - pub steering_message: Option, - /// Messages to send automatically as new turns after the current turn finishes - #[serde(default, skip_serializing_if = "Option::is_none")] - pub queued_messages: Option>, - /// Requests for user input that are currently blocking or informing chat progress - #[serde(default, skip_serializing_if = "Option::is_none")] - pub input_requests: Option>, - /// Additional provider-specific metadata for this chat. - #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] - pub meta: Option, -} - -/// Lightweight catalog entry for a chat, carried in -/// {@link SessionState.chats | `SessionState.chats`}. The full conversation -/// lives in {@link ChatState}, which inlines (denormalizes) every field below. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ChatSummary { - /// Chat URI - pub resource: Uri, - /// Chat title - pub title: String, - /// Current chat status (reuses SessionStatus shape) - pub status: u32, - /// Human-readable description of what the chat is currently doing - #[serde(default, skip_serializing_if = "Option::is_none")] - pub activity: Option, - /// Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - pub modified_at: String, - /// Optional per-chat model override (defaults to the session's model) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub model: Option, - /// Optional per-chat agent override (defaults to the session's agent) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub agent: Option, - /// How this chat came into existence - #[serde(default, skip_serializing_if = "Option::is_none")] - pub origin: Option, - /// Optional per-chat working directory. - /// - /// If absent, the chat inherits - /// {@link SessionSummary.workingDirectory | the session's working directory}. - /// See {@link ChatState.workingDirectory} for usage notes. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub working_directory: Option, -} - /// Full state for a single session, loaded when a client subscribes to the session's URI. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -868,14 +761,20 @@ pub struct SessionState { /// The client currently providing tools and interactive capabilities to this session #[serde(default, skip_serializing_if = "Option::is_none")] pub active_client: Option, - /// Catalog of chats in this session. - pub chats: Vec, - /// The chat that receives input when the user addresses the session without - /// selecting a specific chat. This is a UI routing hint, not a hierarchy - /// marker — chats remain equal peers at the protocol level. Hosts MAY change - /// this over the session's lifetime. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub default_chat: Option, + /// Completed turns + pub turns: Vec, + /// Currently in-progress turn + #[serde(default, skip_serializing_if = "Option::is_none")] + pub active_turn: Option, + /// Message to inject into the current turn at a convenient point + #[serde(default, skip_serializing_if = "Option::is_none")] + pub steering_message: Option, + /// Messages to send automatically as new turns after the current turn finishes + #[serde(default, skip_serializing_if = "Option::is_none")] + pub queued_messages: Option>, + /// Requests for user input that are currently blocking or informing session progress + #[serde(default, skip_serializing_if = "Option::is_none")] + pub input_requests: Option>, /// Session configuration schema and current values #[serde(default, skip_serializing_if = "Option::is_none")] pub config: Option, @@ -940,39 +839,6 @@ pub struct SessionActiveClient { pub customizations: Option>, } -/// Lightweight catalog entry summarizing one session. Surfaced via -/// {@link RootChannelCommands.listSessions | `root/listSessions`} and -/// `root/sessionAdded`/`root/sessionSummaryChanged` notifications. -/// -/// **Aggregation across chats.** Once a session contains more than one chat, -/// several `SessionSummary` fields are derived from the underlying -/// {@link SessionState.chats | chat catalog}. Producers SHOULD follow these -/// rules so clients that only consume the session summary (e.g. a session -/// list) still see meaningful state: -/// -/// - `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` / -/// `Error` — bits 0–4) from the -/// {@link SessionState.defaultChat | default chat} when present, else from -/// the most recently modified chat. **Promote** `InputNeeded` whenever any -/// chat in the session needs input, and **promote** `Error` whenever any -/// chat is in an error state — both override the default-chat bits. The -/// orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped. -/// - `activity`: mirror the activity string of the default chat, or of the -/// chat currently driving the promoted status bits when a non-default chat -/// wins (e.g. the chat that raised `InputNeeded`). -/// - `modifiedAt`: the max of all chats' `modifiedAt`. -/// - `model` / `agent`: the session-level selection. Per-chat overrides are -/// surfaced on individual {@link ChatSummary} entries, not aggregated up. -/// - `workingDirectory`: the session-level **default**. Individual chats MAY -/// override via {@link ChatSummary.workingDirectory}; aggregating these up -/// is meaningless and SHOULD NOT be attempted. -/// - `changes`: optional roll-up across all chats. Producers MAY sum the -/// per-chat changeset stats or report the most expensive chat's stats — -/// whichever is cheaper for the host to compute. -/// -/// Sessions with a single chat trivially satisfy all of the above (the chat's -/// values pass through unchanged). The rules only matter once a session -/// carries multiple chats. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionSummary { @@ -1003,10 +869,7 @@ pub struct SessionSummary { /// — the session uses the provider's default behavior. #[serde(default, skip_serializing_if = "Option::is_none")] pub agent: Option, - /// The default working directory URI for this session. Individual chats - /// MAY override via {@link ChatSummary.workingDirectory | their own - /// `workingDirectory`}; this field acts as the fallback for any chat that - /// does not. + /// The working directory URI for this session #[serde(default, skip_serializing_if = "Option::is_none")] pub working_directory: Option, /// Aggregate summary of file changes associated with this session. Servers @@ -1195,7 +1058,7 @@ pub struct Message { /// A choice in a select-style question. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputOption { +pub struct SessionInputOption { /// Stable option identifier; for MCP enum values this is the enum string pub id: String, /// Display label @@ -1211,25 +1074,25 @@ pub struct ChatInputOption { /// Value captured for one answer. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputTextAnswerValue { +pub struct SessionInputTextAnswerValue { pub value: String, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputNumberAnswerValue { +pub struct SessionInputNumberAnswerValue { pub value: f64, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputBooleanAnswerValue { +pub struct SessionInputBooleanAnswerValue { pub value: bool, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputSelectedAnswerValue { +pub struct SessionInputSelectedAnswerValue { pub value: String, /// Free-form text entered instead of selecting an option #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1238,7 +1101,7 @@ pub struct ChatInputSelectedAnswerValue { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputSelectedManyAnswerValue { +pub struct SessionInputSelectedManyAnswerValue { pub value: Vec, /// Free-form text entered in addition to selected options #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1247,23 +1110,23 @@ pub struct ChatInputSelectedManyAnswerValue { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputAnswered { +pub struct SessionInputAnswered { /// Answer value - pub value: ChatInputAnswerValue, + pub value: SessionInputAnswerValue, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] -pub struct ChatInputSkipped { +pub struct SessionInputSkipped { /// Free-form reason or value captured while skipping, if any #[serde(default, skip_serializing_if = "Option::is_none")] pub freeform_values: Option>, } -/// Text question within a chat input request. +/// Text question within a session input request. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputTextQuestion { +pub struct SessionInputTextQuestion { /// Stable question identifier used as the key in `answers` pub id: String, /// Short display title @@ -1288,10 +1151,10 @@ pub struct ChatInputTextQuestion { pub default_value: Option, } -/// Numeric question within a chat input request. +/// Numeric question within a session input request. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputNumberQuestion { +pub struct SessionInputNumberQuestion { /// Stable question identifier used as the key in `answers` pub id: String, /// Short display title @@ -1313,10 +1176,10 @@ pub struct ChatInputNumberQuestion { pub default_value: Option, } -/// Boolean question within a chat input request. +/// Boolean question within a session input request. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputBooleanQuestion { +pub struct SessionInputBooleanQuestion { /// Stable question identifier used as the key in `answers` pub id: String, /// Short display title @@ -1332,10 +1195,10 @@ pub struct ChatInputBooleanQuestion { pub default_value: Option, } -/// Single-select question within a chat input request. +/// Single-select question within a session input request. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputSingleSelectQuestion { +pub struct SessionInputSingleSelectQuestion { /// Stable question identifier used as the key in `answers` pub id: String, /// Short display title @@ -1347,16 +1210,16 @@ pub struct ChatInputSingleSelectQuestion { #[serde(default, skip_serializing_if = "Option::is_none")] pub required: Option, /// Options the user may select from - pub options: Vec, + pub options: Vec, /// Whether the user may enter text instead of selecting an option #[serde(default, skip_serializing_if = "Option::is_none")] pub allow_freeform_input: Option, } -/// Multi-select question within a chat input request. +/// Multi-select question within a session input request. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputMultiSelectQuestion { +pub struct SessionInputMultiSelectQuestion { /// Stable question identifier used as the key in `answers` pub id: String, /// Short display title @@ -1368,7 +1231,7 @@ pub struct ChatInputMultiSelectQuestion { #[serde(default, skip_serializing_if = "Option::is_none")] pub required: Option, /// Options the user may select from - pub options: Vec, + pub options: Vec, /// Whether the user may enter text in addition to selecting options #[serde(default, skip_serializing_if = "Option::is_none")] pub allow_freeform_input: Option, @@ -1382,12 +1245,12 @@ pub struct ChatInputMultiSelectQuestion { /// A live request for user input. /// -/// The server creates or replaces requests with `chat/inputRequested`. -/// Clients sync drafts with `chat/inputAnswerChanged` and complete requests -/// with `chat/inputCompleted`. +/// The server creates or replaces requests with `session/inputRequested`. +/// Clients sync drafts with `session/inputAnswerChanged` and complete requests +/// with `session/inputCompleted`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ChatInputRequest { +pub struct SessionInputRequest { /// Stable request identifier pub id: String, /// Display message for the request as a whole @@ -1398,10 +1261,10 @@ pub struct ChatInputRequest { pub url: Option, /// Ordered questions to ask the user #[serde(default, skip_serializing_if = "Option::is_none")] - pub questions: Option>, + pub questions: Option>, /// Current draft or submitted answers, keyed by question ID #[serde(default, skip_serializing_if = "Option::is_none")] - pub answers: Option>, + pub answers: Option>, } /// A zero-based position within a textual document. @@ -1618,7 +1481,7 @@ pub struct MessageAnnotationsAttachment { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MarkdownResponsePart { - /// Part identifier, used by `chat/delta` to target this part for content appends + /// Part identifier, used by `session/delta` to target this part for content appends pub id: String, /// Markdown content pub content: String, @@ -1668,7 +1531,7 @@ pub struct ToolCallResponsePart { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ReasoningResponsePart { - /// Part identifier, used by `chat/reasoning` to target this part for content appends + /// Part identifier, used by `session/reasoning` to target this part for content appends pub id: String, /// Accumulated reasoning text pub content: String, @@ -2701,7 +2564,7 @@ pub struct ToolCallClientContributor { /// Absent for server-side tools. /// /// When set, the identified client is responsible for executing the tool and - /// dispatching `chat/toolCallComplete` with the result. + /// dispatching `session/toolCallComplete` with the result. pub client_id: String, } @@ -2879,7 +2742,7 @@ pub struct ErrorInfo { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Snapshot { - /// The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`) + /// The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`) pub resource: Uri, /// The current state of the resource pub state: SnapshotState, @@ -3203,37 +3066,6 @@ pub struct ResourceChange { // ─── Discriminated Unions ───────────────────────────────────────────── -/// How a chat came into existence. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "kind")] -pub enum ChatOrigin { - /// Created directly by a user. - #[serde(rename = "user")] - User, - /// Forked from a specific turn of another chat. - #[serde(rename = "fork")] - Fork { - /// URI of the chat this one was forked from. - chat: Uri, - /// Turn the fork was taken from. - #[serde(rename = "turnId")] - turn_id: String, - }, - /// Spawned by a tool call in another chat. - #[serde(rename = "tool")] - Tool { - /// URI of the chat whose tool call spawned this one. - chat: Uri, - /// Tool call that spawned this chat. - #[serde(rename = "toolCallId")] - tool_call_id: String, - }, - /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. - /// Reducers treat this as a no-op. - #[serde(untagged)] - Unknown(serde_json::Value), -} - /// A single part of a response stream (text, tool call, reasoning, content reference). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "kind")] @@ -3304,22 +3136,22 @@ pub enum TerminalContentPart { Unknown(serde_json::Value), } -/// One question within a chat input request. +/// One question within a session input request. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "kind")] -pub enum ChatInputQuestion { +pub enum SessionInputQuestion { #[serde(rename = "text")] - Text(ChatInputTextQuestion), + Text(SessionInputTextQuestion), #[serde(rename = "number")] - Number(ChatInputNumberQuestion), + Number(SessionInputNumberQuestion), #[serde(rename = "integer")] - Integer(ChatInputNumberQuestion), + Integer(SessionInputNumberQuestion), #[serde(rename = "boolean")] - Boolean(ChatInputBooleanQuestion), + Boolean(SessionInputBooleanQuestion), #[serde(rename = "single-select")] - SingleSelect(ChatInputSingleSelectQuestion), + SingleSelect(SessionInputSingleSelectQuestion), #[serde(rename = "multi-select")] - MultiSelect(ChatInputMultiSelectQuestion), + MultiSelect(SessionInputMultiSelectQuestion), /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. /// Reducers treat this as a no-op. #[serde(untagged)] @@ -3329,17 +3161,17 @@ pub enum ChatInputQuestion { /// Value captured for one answer. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "kind")] -pub enum ChatInputAnswerValue { +pub enum SessionInputAnswerValue { #[serde(rename = "text")] - Text(ChatInputTextAnswerValue), + Text(SessionInputTextAnswerValue), #[serde(rename = "number")] - Number(ChatInputNumberAnswerValue), + Number(SessionInputNumberAnswerValue), #[serde(rename = "boolean")] - Boolean(ChatInputBooleanAnswerValue), + Boolean(SessionInputBooleanAnswerValue), #[serde(rename = "selected")] - Selected(ChatInputSelectedAnswerValue), + Selected(SessionInputSelectedAnswerValue), #[serde(rename = "selected-many")] - SelectedMany(ChatInputSelectedManyAnswerValue), + SelectedMany(SessionInputSelectedManyAnswerValue), /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. /// Reducers treat this as a no-op. #[serde(untagged)] @@ -3349,13 +3181,13 @@ pub enum ChatInputAnswerValue { /// Draft, submitted, or skipped answer for one question. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "state")] -pub enum ChatInputAnswer { +pub enum SessionInputAnswer { #[serde(rename = "draft")] - Draft(ChatInputAnswered), + Draft(SessionInputAnswered), #[serde(rename = "submitted")] - Submitted(ChatInputAnswered), + Submitted(SessionInputAnswered), #[serde(rename = "skipped")] - Skipped(ChatInputSkipped), + Skipped(SessionInputSkipped), /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. /// Reducers treat this as a no-op. #[serde(untagged)] @@ -3492,19 +3324,17 @@ pub enum ToolCallContributor { Unknown(serde_json::Value), } -/// The state payload of a snapshot — root, session, chat, terminal, +/// The state payload of a snapshot — root, session, terminal, /// changeset, resource-watch, or annotations state. /// /// Deserialized by trying session first (has required `summary`), then -/// chat (has required `turns`), then terminal (has required `content`), -/// then changeset (has required `status` and `files`), then resource-watch -/// (has required `root` and `recursive`), then annotations (has required -/// `annotations`), then root. +/// terminal (has required `content`), then changeset (has required +/// `status` and `files`), then resource-watch (has required `root` and +/// `recursive`), then annotations (has required `annotations`), then root. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum SnapshotState { Session(Box), - Chat(Box), Terminal(Box), Changeset(Box), ResourceWatch(Box), diff --git a/clients/rust/crates/ahp/src/multi_host_state_mirror.rs b/clients/rust/crates/ahp/src/multi_host_state_mirror.rs index 7288894b..b224c41f 100644 --- a/clients/rust/crates/ahp/src/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/src/multi_host_state_mirror.rs @@ -37,14 +37,12 @@ use std::collections::HashMap; use ahp_types::actions::ActionEnvelope; use ahp_types::common::ROOT_RESOURCE_URI; use ahp_types::state::{ - AnnotationsState, ChangesetState, ChatState, ResourceWatchState, RootState, SessionState, - SnapshotState, TerminalState, + AnnotationsState, ChangesetState, ResourceWatchState, RootState, SessionState, SnapshotState, + TerminalState, }; use crate::hosts::{HostId, HostSubscriptionEvent}; -use crate::reducers::{ - apply_action_to_chat, apply_action_to_root, apply_action_to_session, apply_action_to_terminal, -}; +use crate::reducers::{apply_action_to_root, apply_action_to_session, apply_action_to_terminal}; use crate::SubscriptionEvent; /// Compound key tagging a channel URI with the host that produced it. @@ -87,7 +85,6 @@ impl HostedResourceKey { pub struct MultiHostStateMirror { root_states: HashMap, sessions: HashMap, - chats: HashMap, terminals: HashMap, changesets: HashMap, annotations: HashMap, @@ -110,11 +107,6 @@ impl MultiHostStateMirror { &self.sessions } - /// Borrow the chat states map keyed by `(host_id, uri)`. - pub fn chats(&self) -> &HashMap { - &self.chats - } - /// Borrow the terminal states map keyed by `(host_id, uri)`. pub fn terminals(&self) -> &HashMap { &self.terminals @@ -170,10 +162,6 @@ impl MultiHostStateMirror { apply_action_to_session(session, &envelope.action); return; } - if let Some(chat) = self.chats.get_mut(&key) { - apply_action_to_chat(chat, &envelope.action); - return; - } if let Some(terminal) = self.terminals.get_mut(&key) { apply_action_to_terminal(terminal, &envelope.action); } @@ -196,9 +184,6 @@ impl MultiHostStateMirror { SnapshotState::Session(state) => { self.sessions.insert(key, state.as_ref().clone()); } - SnapshotState::Chat(state) => { - self.chats.insert(key, state.as_ref().clone()); - } SnapshotState::Terminal(state) => { self.terminals.insert(key, state.as_ref().clone()); } @@ -219,7 +204,6 @@ impl MultiHostStateMirror { pub fn reset_host(&mut self, host: &HostId) { self.root_states.remove(host); self.sessions.retain(|key, _| &key.host_id != host); - self.chats.retain(|key, _| &key.host_id != host); self.terminals.retain(|key, _| &key.host_id != host); self.changesets.retain(|key, _| &key.host_id != host); self.annotations.retain(|key, _| &key.host_id != host); @@ -230,7 +214,6 @@ impl MultiHostStateMirror { pub fn reset(&mut self) { self.root_states.clear(); self.sessions.clear(); - self.chats.clear(); self.terminals.clear(); self.changesets.clear(); self.annotations.clear(); diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index df3e279f..b7d57853 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -1,13 +1,13 @@ //! Pure state reducers ported from `types/reducers.ts`. //! //! Reducers mutate state in place and return a [`ReduceOutcome`]. Use -//! [`apply_action_to_root`], [`apply_action_to_session`], -//! [`apply_action_to_chat`], and [`apply_action_to_terminal`] to -//! dispatch any [`StateAction`] against the matching scope; unrelated -//! actions short-circuit as [`ReduceOutcome::OutOfScope`] so a client -//! holding every state tree can blindly fan each action out. +//! [`apply_action_to_root`], [`apply_action_to_session`], and +//! [`apply_action_to_terminal`] to dispatch any [`StateAction`] against +//! the matching scope; unrelated actions short-circuit as +//! [`ReduceOutcome::OutOfScope`] so a client holding all three state +//! trees can blindly fan every action out. //! -//! All reducers are pure functions over `(state, action)` — no +//! All three reducers are pure functions over `(state, action)` — no //! I/O, no allocation beyond what the action itself carries — which //! makes them safe to run inside a UI render loop or a snapshot //! reconciler. @@ -50,13 +50,13 @@ use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; use ahp_types::actions::{ - ChatInputAnswerChangedAction, ChatToolCallCompleteAction, ChatToolCallConfirmedAction, - ChatToolCallContentChangedAction, ChatToolCallDeltaAction, ChatToolCallReadyAction, - ChatToolCallResultConfirmedAction, ChatTurnStartedAction, StateAction, + SessionInputAnswerChangedAction, SessionToolCallCompleteAction, SessionToolCallConfirmedAction, + SessionToolCallContentChangedAction, SessionToolCallDeltaAction, SessionToolCallReadyAction, + SessionToolCallResultConfirmedAction, SessionTurnStartedAction, StateAction, }; use ahp_types::state::{ - ActiveTurn, ChatInputRequest, ChatState, ChildCustomization, ConfirmationOption, Customization, - ErrorInfo, PendingMessage, PendingMessageKind, ResponsePart, RootState, SessionLifecycle, + ActiveTurn, ChildCustomization, ConfirmationOption, Customization, ErrorInfo, PendingMessage, + PendingMessageKind, ResponsePart, RootState, SessionInputRequest, SessionLifecycle, SessionState, SessionStatus, TerminalCommandPart, TerminalContentPart, TerminalState, TerminalUnclassifiedPart, ToolCallCancellationReason, ToolCallCancelledState, ToolCallCompletedState, ToolCallConfirmationReason, ToolCallContributor, @@ -95,33 +95,6 @@ fn now_ms() -> i64 { .unwrap_or(0) } -fn now_iso() -> String { - iso8601_from_unix_millis(now_ms()) -} - -fn iso8601_from_unix_millis(ms: i64) -> String { - let seconds = ms.div_euclid(1_000); - let millis = ms.rem_euclid(1_000); - let days = seconds.div_euclid(86_400); - let seconds_of_day = seconds.rem_euclid(86_400); - let hour = seconds_of_day / 3_600; - let minute = (seconds_of_day % 3_600) / 60; - let second = seconds_of_day % 60; - - let z = days + 719_468; - let era = if z >= 0 { z } else { z - 146_096 }.div_euclid(146_097); - let doe = z - era * 146_097; - let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365; - let y = yoe + era * 400; - let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); - let mp = (5 * doy + 2) / 153; - let day = doy - (153 * mp + 2) / 5 + 1; - let month = mp + if mp < 10 { 3 } else { -9 }; - let year = y + if month <= 2 { 1 } else { 0 }; - - format!("{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{millis:03}Z") -} - fn tool_call_meta( tc: &ToolCallState, ) -> ( @@ -190,7 +163,7 @@ fn tool_call_id(tc: &ToolCallState) -> &str { } } -fn has_pending_tool_call_confirmation(state: &ChatState) -> bool { +fn has_pending_tool_call_confirmation(state: &SessionState) -> bool { let Some(active) = &state.active_turn else { return false; }; @@ -216,7 +189,7 @@ fn with_status_flag(status: u32, flag: SessionStatus, set: bool) -> u32 { } } -fn summary_status(state: &ChatState, terminal: Option) -> u32 { +fn summary_status(state: &SessionState, terminal: Option) -> u32 { let activity: u32 = if let Some(t) = terminal { t as u32 } else if state @@ -232,23 +205,19 @@ fn summary_status(state: &ChatState, terminal: Option) -> u32 { } else { SessionStatus::Idle as u32 }; - (state.status & !STATUS_ACTIVITY_MASK) | activity + (state.summary.status & !STATUS_ACTIVITY_MASK) | activity } -fn refresh_summary_status(state: &mut ChatState) { - state.status = summary_status(state, None); +fn refresh_summary_status(state: &mut SessionState) { + state.summary.status = summary_status(state, None); } -fn touch_chat_modified(state: &mut ChatState) { - state.modified_at = now_iso(); -} - -fn touch_session_modified(state: &mut SessionState) { +fn touch_modified(state: &mut SessionState) { state.summary.modified_at = now_ms(); } fn end_turn( - state: &mut ChatState, + state: &mut SessionState, turn_id: &str, turn_state: TurnState, terminal_status: Option, @@ -327,12 +296,12 @@ fn end_turn( state.turns.push(turn); state.input_requests = None; - touch_chat_modified(state); - state.status = summary_status(state, terminal_status); + touch_modified(state); + state.summary.status = summary_status(state, terminal_status); ReduceOutcome::Applied } -fn upsert_input_request(state: &mut ChatState, request: ChatInputRequest) { +fn upsert_input_request(state: &mut SessionState, request: SessionInputRequest) { let existing = state.input_requests.get_or_insert_with(Vec::new); if let Some(idx) = existing.iter().position(|r| r.id == request.id) { let answers = request @@ -345,9 +314,9 @@ fn upsert_input_request(state: &mut ChatState, request: ChatInputRequest) { } else { existing.push(request); } - state.status = summary_status(state, None); - touch_chat_modified(state); - state.status = with_status_flag(state.status, SessionStatus::IsRead, false); + state.summary.status = summary_status(state, None); + touch_modified(state); + state.summary.status = with_status_flag(state.summary.status, SessionStatus::IsRead, false); } // ─── Customization Helpers ─────────────────────────────────────────────────── @@ -399,7 +368,7 @@ fn apply_toggle(list: &mut [Customization], id: &str, enabled: bool) -> bool { } fn update_tool_call( - state: &mut ChatState, + state: &mut SessionState, turn_id: &str, tool_call_id_target: &str, updater: F, @@ -441,7 +410,7 @@ where } fn update_response_part( - state: &mut ChatState, + state: &mut SessionState, turn_id: &str, part_id: &str, updater: F, @@ -522,75 +491,117 @@ pub fn apply_action_to_session(state: &mut SessionState, action: &StateAction) - state.creation_error = Some(a.error.clone()); ReduceOutcome::Applied } - StateAction::SessionChatAdded(a) => { - if let Some(idx) = state - .chats - .iter() - .position(|chat| chat.resource == a.summary.resource) - { - state.chats[idx] = a.summary.clone(); - } else { - state.chats.push(a.summary.clone()); + StateAction::SessionTurnStarted(a) => apply_turn_started(state, a), + StateAction::SessionDelta(a) => update_response_part(state, &a.turn_id, &a.part_id, |p| { + if let ResponsePart::Markdown(m) = p { + m.content.push_str(&a.content); } - ReduceOutcome::Applied - } - StateAction::SessionChatRemoved(a) => { - let Some(idx) = state.chats.iter().position(|chat| chat.resource == a.chat) else { + }), + StateAction::SessionResponsePart(a) => { + let Some(active) = state.active_turn.as_mut() else { return ReduceOutcome::NoOp; }; - state.chats.remove(idx); - if state.default_chat.as_ref() == Some(&a.chat) { - state.default_chat = None; + if active.id != a.turn_id { + return ReduceOutcome::NoOp; } + active.response_parts.push(a.part.clone()); ReduceOutcome::Applied } - StateAction::SessionChatUpdated(a) => { - let Some(chat) = state.chats.iter_mut().find(|chat| chat.resource == a.chat) else { + StateAction::SessionTurnComplete(a) => { + end_turn(state, &a.turn_id, TurnState::Complete, None, None) + } + StateAction::SessionTurnCancelled(a) => { + end_turn(state, &a.turn_id, TurnState::Cancelled, None, None) + } + StateAction::SessionError(a) => end_turn( + state, + &a.turn_id, + TurnState::Error, + Some(SessionStatus::Error), + Some(a.error.clone()), + ), + StateAction::SessionToolCallStart(a) => { + let Some(active) = state.active_turn.as_mut() else { return ReduceOutcome::NoOp; }; - if let Some(title) = &a.changes.title { - chat.title = title.clone(); - } - if let Some(status) = a.changes.status { - chat.status = status; - } - if let Some(activity) = &a.changes.activity { - chat.activity = Some(activity.clone()); - } - if let Some(modified_at) = &a.changes.modified_at { - chat.modified_at = modified_at.clone(); - } - if let Some(model) = &a.changes.model { - chat.model = Some(model.clone()); + if active.id != a.turn_id { + return ReduceOutcome::NoOp; } - if let Some(agent) = &a.changes.agent { - chat.agent = Some(agent.clone()); + active + .response_parts + .push(ResponsePart::ToolCall(Box::new(ToolCallResponsePart { + tool_call: ToolCallState::Streaming(ToolCallStreamingState { + tool_call_id: a.tool_call_id.clone(), + tool_name: a.tool_name.clone(), + display_name: a.display_name.clone(), + contributor: a.contributor.clone(), + meta: a.meta.clone(), + partial_input: None, + invocation_message: None, + }), + }))); + ReduceOutcome::Applied + } + StateAction::SessionToolCallDelta(a) => apply_tool_call_delta(state, a), + StateAction::SessionToolCallReady(a) => { + let res = apply_tool_call_ready(state, a); + if res == ReduceOutcome::Applied { + refresh_summary_status(state); } - if let Some(origin) = &a.changes.origin { - chat.origin = Some(origin.clone()); + res + } + StateAction::SessionToolCallConfirmed(a) => { + let res = apply_tool_call_confirmed(state, a); + if res == ReduceOutcome::Applied { + refresh_summary_status(state); } - if let Some(working_directory) = &a.changes.working_directory { - chat.working_directory = Some(working_directory.clone()); + res + } + StateAction::SessionToolCallComplete(a) => { + let res = apply_tool_call_complete(state, a); + if res == ReduceOutcome::Applied { + refresh_summary_status(state); } - ReduceOutcome::Applied + res } - StateAction::SessionDefaultChatChanged(a) => { - state.default_chat = a.default_chat.clone(); - ReduceOutcome::Applied + StateAction::SessionToolCallResultConfirmed(a) => { + let res = apply_tool_call_result_confirmed(state, a); + if res == ReduceOutcome::Applied { + refresh_summary_status(state); + } + res } + StateAction::SessionToolCallContentChanged(a) => apply_tool_call_content_changed(state, a), StateAction::SessionTitleChanged(a) => { state.summary.title = a.title.clone(); - touch_session_modified(state); + touch_modified(state); ReduceOutcome::Applied } + StateAction::SessionUsage(a) => { + let Some(active) = state.active_turn.as_mut() else { + return ReduceOutcome::NoOp; + }; + if active.id != a.turn_id { + return ReduceOutcome::NoOp; + } + active.usage = Some(a.usage.clone()); + ReduceOutcome::Applied + } + StateAction::SessionReasoning(a) => { + update_response_part(state, &a.turn_id, &a.part_id, |p| { + if let ResponsePart::Reasoning(r) = p { + r.content.push_str(&a.content); + } + }) + } StateAction::SessionModelChanged(a) => { state.summary.model = Some(a.model.clone()); - touch_session_modified(state); + touch_modified(state); ReduceOutcome::Applied } StateAction::SessionAgentChanged(a) => { state.summary.agent = a.agent.clone(); - touch_session_modified(state); + touch_modified(state); ReduceOutcome::Applied } StateAction::SessionIsReadChanged(a) => { @@ -625,7 +636,7 @@ pub fn apply_action_to_session(state: &mut SessionState, action: &StateAction) - config.values.insert(k.clone(), v.clone()); } } - touch_session_modified(state); + touch_modified(state); ReduceOutcome::Applied } StateAction::SessionMetaChanged(a) => { @@ -740,122 +751,13 @@ pub fn apply_action_to_session(state: &mut SessionState, action: &StateAction) - } ReduceOutcome::NoOp } - _ => ReduceOutcome::OutOfScope, - } -} - -// ─── Chat Reducer ───────────────────────────────────────────────────── - -/// Apply a [`StateAction`] to a [`ChatState`] in place. -/// -/// Handles all chat-scoped actions — turn lifecycle, tool calls, input -/// requests, and pending/queued messages. Actions targeting a different -/// scope short-circuit as [`ReduceOutcome::OutOfScope`]. -pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> ReduceOutcome { - match action { - StateAction::ChatTurnStarted(a) => apply_turn_started(state, a), - StateAction::ChatDelta(a) => update_response_part(state, &a.turn_id, &a.part_id, |p| { - if let ResponsePart::Markdown(m) = p { - m.content.push_str(&a.content); - } - }), - StateAction::ChatResponsePart(a) => { - let Some(active) = state.active_turn.as_mut() else { - return ReduceOutcome::NoOp; - }; - if active.id != a.turn_id { - return ReduceOutcome::NoOp; - } - active.response_parts.push(a.part.clone()); - ReduceOutcome::Applied - } - StateAction::ChatTurnComplete(a) => { - end_turn(state, &a.turn_id, TurnState::Complete, None, None) - } - StateAction::ChatTurnCancelled(a) => { - end_turn(state, &a.turn_id, TurnState::Cancelled, None, None) - } - StateAction::ChatError(a) => end_turn( - state, - &a.turn_id, - TurnState::Error, - Some(SessionStatus::Error), - Some(a.error.clone()), - ), - StateAction::ChatToolCallStart(a) => { - let Some(active) = state.active_turn.as_mut() else { - return ReduceOutcome::NoOp; - }; - if active.id != a.turn_id { - return ReduceOutcome::NoOp; - } - active - .response_parts - .push(ResponsePart::ToolCall(Box::new(ToolCallResponsePart { - tool_call: ToolCallState::Streaming(ToolCallStreamingState { - tool_call_id: a.tool_call_id.clone(), - tool_name: a.tool_name.clone(), - display_name: a.display_name.clone(), - contributor: a.contributor.clone(), - meta: a.meta.clone(), - partial_input: None, - invocation_message: None, - }), - }))); - ReduceOutcome::Applied - } - StateAction::ChatToolCallDelta(a) => apply_tool_call_delta(state, a), - StateAction::ChatToolCallReady(a) => { - let res = apply_tool_call_ready(state, a); - if res == ReduceOutcome::Applied { - refresh_summary_status(state); - } - res - } - StateAction::ChatToolCallConfirmed(a) => { - let res = apply_tool_call_confirmed(state, a); - if res == ReduceOutcome::Applied { - refresh_summary_status(state); - } - res - } - StateAction::ChatToolCallComplete(a) => { - let res = apply_tool_call_complete(state, a); - if res == ReduceOutcome::Applied { - refresh_summary_status(state); - } - res - } - StateAction::ChatToolCallResultConfirmed(a) => { - let res = apply_tool_call_result_confirmed(state, a); - if res == ReduceOutcome::Applied { - refresh_summary_status(state); - } - res - } - StateAction::ChatToolCallContentChanged(a) => apply_tool_call_content_changed(state, a), - StateAction::ChatUsage(a) => { - let Some(active) = state.active_turn.as_mut() else { - return ReduceOutcome::NoOp; - }; - if active.id != a.turn_id { - return ReduceOutcome::NoOp; - } - active.usage = Some(a.usage.clone()); - ReduceOutcome::Applied - } - StateAction::ChatReasoning(a) => update_response_part(state, &a.turn_id, &a.part_id, |p| { - if let ResponsePart::Reasoning(r) = p { - r.content.push_str(&a.content); - } - }), - StateAction::ChatTruncated(a) => apply_truncated(state, a.turn_id.as_deref()), - StateAction::ChatInputRequested(a) => { + StateAction::SessionTruncated(a) => apply_truncated(state, a.turn_id.as_deref()), + StateAction::SessionInputRequested(a) => { upsert_input_request(state, a.request.clone()); ReduceOutcome::Applied } - StateAction::ChatInputAnswerChanged(a) => apply_input_answer_changed(state, a), - StateAction::ChatInputCompleted(a) => { + StateAction::SessionInputAnswerChanged(a) => apply_input_answer_changed(state, a), + StateAction::SessionInputCompleted(a) => { let Some(list) = state.input_requests.as_mut() else { return ReduceOutcome::NoOp; }; @@ -868,10 +770,10 @@ pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> Redu state.input_requests = None; } refresh_summary_status(state); - touch_chat_modified(state); + touch_modified(state); ReduceOutcome::Applied } - StateAction::ChatPendingMessageSet(a) => { + StateAction::SessionPendingMessageSet(a) => { let entry = PendingMessage { id: a.id.clone(), message: a.message.clone(), @@ -891,7 +793,7 @@ pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> Redu } ReduceOutcome::Applied } - StateAction::ChatPendingMessageRemoved(a) => match a.kind { + StateAction::SessionPendingMessageRemoved(a) => match a.kind { PendingMessageKind::Steering => match &state.steering_message { Some(m) if m.id == a.id => { state.steering_message = None; @@ -914,7 +816,7 @@ pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> Redu ReduceOutcome::Applied } }, - StateAction::ChatQueuedMessagesReordered(a) => { + StateAction::SessionQueuedMessagesReordered(a) => { let Some(list) = state.queued_messages.as_mut() else { return ReduceOutcome::NoOp; }; @@ -940,16 +842,16 @@ pub fn apply_action_to_chat(state: &mut ChatState, action: &StateAction) -> Redu } } -fn apply_turn_started(state: &mut ChatState, a: &ChatTurnStartedAction) -> ReduceOutcome { +fn apply_turn_started(state: &mut SessionState, a: &SessionTurnStartedAction) -> ReduceOutcome { state.active_turn = Some(ActiveTurn { id: a.turn_id.clone(), message: a.message.clone(), response_parts: Vec::new(), usage: None, }); - state.status = summary_status(state, None); - touch_chat_modified(state); - state.status = with_status_flag(state.status, SessionStatus::IsRead, false); + state.summary.status = summary_status(state, None); + touch_modified(state); + state.summary.status = with_status_flag(state.summary.status, SessionStatus::IsRead, false); if let Some(qmid) = &a.queued_message_id { if state.steering_message.as_ref().map(|m| m.id.as_str()) == Some(qmid.as_str()) { @@ -965,7 +867,10 @@ fn apply_turn_started(state: &mut ChatState, a: &ChatTurnStartedAction) -> Reduc ReduceOutcome::Applied } -fn apply_tool_call_delta(state: &mut ChatState, a: &ChatToolCallDeltaAction) -> ReduceOutcome { +fn apply_tool_call_delta( + state: &mut SessionState, + a: &SessionToolCallDeltaAction, +) -> ReduceOutcome { update_tool_call(state, &a.turn_id, &a.tool_call_id, |tc| match tc { ToolCallState::Streaming(mut s) => { let current = s.partial_input.unwrap_or_default(); @@ -982,7 +887,10 @@ fn apply_tool_call_delta(state: &mut ChatState, a: &ChatToolCallDeltaAction) -> }) } -fn apply_tool_call_ready(state: &mut ChatState, a: &ChatToolCallReadyAction) -> ReduceOutcome { +fn apply_tool_call_ready( + state: &mut SessionState, + a: &SessionToolCallReadyAction, +) -> ReduceOutcome { update_tool_call(state, &a.turn_id, &a.tool_call_id, |tc| { let (tool_call_id, tool_name, display_name, contributor, meta) = tool_call_meta(&tc); let meta = a.meta.clone().or(meta); @@ -1032,8 +940,8 @@ fn resolve_selected_option( } fn apply_tool_call_confirmed( - state: &mut ChatState, - a: &ChatToolCallConfirmedAction, + state: &mut SessionState, + a: &SessionToolCallConfirmedAction, ) -> ReduceOutcome { update_tool_call(state, &a.turn_id, &a.tool_call_id, |tc| { let ToolCallState::PendingConfirmation(s) = tc else { @@ -1080,8 +988,8 @@ fn apply_tool_call_confirmed( } fn apply_tool_call_complete( - state: &mut ChatState, - a: &ChatToolCallCompleteAction, + state: &mut SessionState, + a: &SessionToolCallCompleteAction, ) -> ReduceOutcome { update_tool_call(state, &a.turn_id, &a.tool_call_id, |tc| { let (tool_call_id, tool_name, display_name, contributor, meta) = tool_call_meta(&tc); @@ -1140,8 +1048,8 @@ fn apply_tool_call_complete( } fn apply_tool_call_result_confirmed( - state: &mut ChatState, - a: &ChatToolCallResultConfirmedAction, + state: &mut SessionState, + a: &SessionToolCallResultConfirmedAction, ) -> ReduceOutcome { update_tool_call(state, &a.turn_id, &a.tool_call_id, |tc| { let ToolCallState::PendingResultConfirmation(s) = tc else { @@ -1183,8 +1091,8 @@ fn apply_tool_call_result_confirmed( } fn apply_tool_call_content_changed( - state: &mut ChatState, - a: &ChatToolCallContentChangedAction, + state: &mut SessionState, + a: &SessionToolCallContentChangedAction, ) -> ReduceOutcome { update_tool_call(state, &a.turn_id, &a.tool_call_id, |tc| match tc { ToolCallState::Running(mut s) => { @@ -1198,7 +1106,7 @@ fn apply_tool_call_content_changed( }) } -fn apply_truncated(state: &mut ChatState, turn_id: Option<&str>) -> ReduceOutcome { +fn apply_truncated(state: &mut SessionState, turn_id: Option<&str>) -> ReduceOutcome { match turn_id { None => { state.turns.clear(); @@ -1212,14 +1120,14 @@ fn apply_truncated(state: &mut ChatState, turn_id: Option<&str>) -> ReduceOutcom } state.active_turn = None; state.input_requests = None; - touch_chat_modified(state); - state.status = summary_status(state, None); + touch_modified(state); + state.summary.status = summary_status(state, None); ReduceOutcome::Applied } fn apply_input_answer_changed( - state: &mut ChatState, - a: &ChatInputAnswerChangedAction, + state: &mut SessionState, + a: &SessionInputAnswerChangedAction, ) -> ReduceOutcome { let Some(list) = state.input_requests.as_mut() else { return ReduceOutcome::NoOp; @@ -1240,7 +1148,7 @@ fn apply_input_answer_changed( if answers.is_empty() { req.answers = None; } - touch_chat_modified(state); + touch_modified(state); ReduceOutcome::Applied } @@ -1333,7 +1241,7 @@ pub fn apply_action_to_terminal(state: &mut TerminalState, action: &StateAction) #[cfg(test)] mod tests { use super::*; - use ahp_types::state::{ChatSummary, MarkdownResponsePart, Message, SessionSummary}; + use ahp_types::state::{MarkdownResponsePart, Message, SessionSummary}; fn user_message(text: &str) -> Message { Message { @@ -1365,54 +1273,37 @@ mod tests { creation_error: None, server_tools: None, active_client: None, - chats: Vec::new(), - default_chat: None, - config: None, - customizations: None, - changesets: None, - meta: None, - } - } - - fn empty_chat(resource: &str) -> ChatState { - ChatState { - resource: resource.to_string(), - title: String::new(), - status: SessionStatus::Idle as u32, - activity: None, - modified_at: "1970-01-01T00:00:00.000Z".into(), - model: None, - agent: None, - origin: None, - working_directory: None, turns: Vec::new(), active_turn: None, steering_message: None, queued_messages: None, input_requests: None, + config: None, + customizations: None, + changesets: None, meta: None, } } #[test] fn turn_started_creates_active_turn_and_sets_in_progress() { - let mut s = empty_chat("copilot:/s1/chat/1"); - let action = StateAction::ChatTurnStarted(ChatTurnStartedAction { + let mut s = empty_session("copilot:/s1"); + let action = StateAction::SessionTurnStarted(SessionTurnStartedAction { turn_id: "t1".into(), message: user_message("hi"), queued_message_id: None, }); assert_eq!( - apply_action_to_chat(&mut s, &action), + apply_action_to_session(&mut s, &action), ReduceOutcome::Applied ); - assert_eq!(s.status, SessionStatus::InProgress as u32); + assert_eq!(s.summary.status, SessionStatus::InProgress as u32); assert_eq!(s.active_turn.unwrap().id, "t1"); } #[test] fn delta_appends_to_markdown_part() { - let mut s = empty_chat("copilot:/s1/chat/1"); + let mut s = empty_session("copilot:/s1"); s.active_turn = Some(ActiveTurn { id: "t1".into(), message: user_message("hi"), @@ -1422,12 +1313,12 @@ mod tests { })], usage: None, }); - let a = StateAction::ChatDelta(ahp_types::actions::ChatDeltaAction { + let a = StateAction::SessionDelta(ahp_types::actions::SessionDeltaAction { turn_id: "t1".into(), part_id: "p1".into(), content: ", world".into(), }); - assert_eq!(apply_action_to_chat(&mut s, &a), ReduceOutcome::Applied); + assert_eq!(apply_action_to_session(&mut s, &a), ReduceOutcome::Applied); match &s.active_turn.unwrap().response_parts[0] { ResponsePart::Markdown(m) => assert_eq!(m.content, "Hello, world"), _ => panic!(), @@ -1436,90 +1327,22 @@ mod tests { #[test] fn turn_complete_moves_active_to_turns_and_returns_idle() { - let mut s = empty_chat("copilot:/s1/chat/1"); + let mut s = empty_session("copilot:/s1"); s.active_turn = Some(ActiveTurn { id: "t1".into(), message: user_message("hi"), response_parts: Vec::new(), usage: None, }); - s.status = SessionStatus::InProgress as u32; - let a = StateAction::ChatTurnComplete(ahp_types::actions::ChatTurnCompleteAction { + s.summary.status = SessionStatus::InProgress as u32; + let a = StateAction::SessionTurnComplete(ahp_types::actions::SessionTurnCompleteAction { turn_id: "t1".into(), }); - assert_eq!(apply_action_to_chat(&mut s, &a), ReduceOutcome::Applied); + assert_eq!(apply_action_to_session(&mut s, &a), ReduceOutcome::Applied); assert!(s.active_turn.is_none()); assert_eq!(s.turns.len(), 1); assert_eq!(s.turns[0].state, TurnState::Complete); - assert_eq!(s.status, SessionStatus::Idle as u32); - } - - #[test] - fn session_reducer_handles_ready_and_chat_catalog_actions() { - let mut s = empty_session("copilot:/s1"); - let ready = StateAction::SessionReady(ahp_types::actions::SessionReadyAction {}); - assert_eq!( - apply_action_to_session(&mut s, &ready), - ReduceOutcome::Applied - ); - assert_eq!(s.lifecycle, SessionLifecycle::Ready); - - let chat = ChatSummary { - resource: "copilot:/s1/chat/1".into(), - title: "c1".into(), - status: SessionStatus::Idle as u32, - activity: None, - modified_at: "1970-01-01T00:00:00.000Z".into(), - model: None, - agent: None, - origin: None, - working_directory: None, - }; - let added = StateAction::SessionChatAdded(ahp_types::actions::SessionChatAddedAction { - summary: chat.clone(), - }); - assert_eq!( - apply_action_to_session(&mut s, &added), - ReduceOutcome::Applied - ); - assert_eq!(s.chats, vec![chat.clone()]); - - let updated = - StateAction::SessionChatUpdated(ahp_types::actions::SessionChatUpdatedAction { - chat: chat.resource.clone(), - changes: ahp_types::actions::PartialChatSummary { - title: Some("renamed".into()), - modified_at: Some("1970-01-01T00:00:09.999Z".into()), - ..Default::default() - }, - }); - assert_eq!( - apply_action_to_session(&mut s, &updated), - ReduceOutcome::Applied - ); - assert_eq!(s.chats[0].title, "renamed"); - assert_eq!(s.chats[0].modified_at, "1970-01-01T00:00:09.999Z"); - - s.default_chat = Some(chat.resource.clone()); - let removed = - StateAction::SessionChatRemoved(ahp_types::actions::SessionChatRemovedAction { - chat: chat.resource.clone(), - }); - assert_eq!( - apply_action_to_session(&mut s, &removed), - ReduceOutcome::Applied - ); - assert!(s.chats.is_empty()); - assert!(s.default_chat.is_none()); - - // A chat-scoped action is out of scope for the session reducer. - let turn = StateAction::ChatTurnComplete(ahp_types::actions::ChatTurnCompleteAction { - turn_id: "t1".into(), - }); - assert_eq!( - apply_action_to_session(&mut s, &turn), - ReduceOutcome::OutOfScope - ); + assert_eq!(s.summary.status, SessionStatus::Idle as u32); } #[test] @@ -1732,14 +1555,6 @@ mod tests { &file_name, description, ), - "chat" => run_fixture::( - initial, - expected, - &parsed_actions, - apply_action_to_chat, - &file_name, - description, - ), "terminal" => run_fixture::( initial, expected, diff --git a/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs b/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs index 442597ad..6b46b6b8 100644 --- a/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs +++ b/clients/rust/crates/ahp/tests/multi_host_state_mirror.rs @@ -64,8 +64,11 @@ fn session_state(title: &str, resource: &str) -> SessionState { creation_error: None, server_tools: None, active_client: None, - chats: vec![], - default_chat: None, + turns: vec![], + active_turn: None, + steering_message: None, + queued_messages: None, + input_requests: None, config: None, customizations: None, changesets: None, diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift index ded019d4..a80a4215 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Actions.generated.swift @@ -10,43 +10,39 @@ public enum ActionType: String, Codable, Sendable { 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 chatTurnComplete = "chat/turnComplete" - case chatTurnCancelled = "chat/turnCancelled" - case chatError = "chat/error" + case sessionTurnStarted = "session/turnStarted" + case sessionDelta = "session/delta" + case sessionResponsePart = "session/responsePart" + case sessionToolCallStart = "session/toolCallStart" + case sessionToolCallDelta = "session/toolCallDelta" + case sessionToolCallReady = "session/toolCallReady" + case sessionToolCallConfirmed = "session/toolCallConfirmed" + case sessionToolCallComplete = "session/toolCallComplete" + case sessionToolCallResultConfirmed = "session/toolCallResultConfirmed" + case sessionToolCallContentChanged = "session/toolCallContentChanged" + case sessionTurnComplete = "session/turnComplete" + case sessionTurnCancelled = "session/turnCancelled" + case sessionError = "session/error" case sessionTitleChanged = "session/titleChanged" - case chatUsage = "chat/usage" - case chatReasoning = "chat/reasoning" + case sessionUsage = "session/usage" + case sessionReasoning = "session/reasoning" case sessionModelChanged = "session/modelChanged" case sessionAgentChanged = "session/agentChanged" case sessionServerToolsChanged = "session/serverToolsChanged" case sessionActiveClientChanged = "session/activeClientChanged" case sessionActiveClientToolsChanged = "session/activeClientToolsChanged" - case chatPendingMessageSet = "chat/pendingMessageSet" - case chatPendingMessageRemoved = "chat/pendingMessageRemoved" - case chatQueuedMessagesReordered = "chat/queuedMessagesReordered" - case chatInputRequested = "chat/inputRequested" - case chatInputAnswerChanged = "chat/inputAnswerChanged" - case chatInputCompleted = "chat/inputCompleted" + case sessionPendingMessageSet = "session/pendingMessageSet" + case sessionPendingMessageRemoved = "session/pendingMessageRemoved" + case sessionQueuedMessagesReordered = "session/queuedMessagesReordered" + case sessionInputRequested = "session/inputRequested" + case sessionInputAnswerChanged = "session/inputAnswerChanged" + case sessionInputCompleted = "session/inputCompleted" case sessionCustomizationsChanged = "session/customizationsChanged" case sessionCustomizationToggled = "session/customizationToggled" case sessionCustomizationUpdated = "session/customizationUpdated" case sessionCustomizationRemoved = "session/customizationRemoved" case sessionMcpServerStateChanged = "session/mcpServerStateChanged" - case chatTruncated = "chat/truncated" + case sessionTruncated = "session/truncated" case sessionIsReadChanged = "session/isReadChanged" case sessionIsArchivedChanged = "session/isArchivedChanged" case sessionActivityChanged = "session/activityChanged" @@ -172,70 +168,7 @@ public struct SessionCreationFailedAction: Codable, Sendable { } } -public struct SessionChatAddedAction: Codable, Sendable { - public var type: ActionType - /// The full summary of the newly added (or upserted) chat. - public var summary: ChatSummary - - public init( - type: ActionType, - summary: ChatSummary - ) { - self.type = type - self.summary = summary - } -} - -public struct SessionChatRemovedAction: Codable, Sendable { - public var type: ActionType - /// The URI of the chat to remove. - public var chat: String - - public init( - type: ActionType, - chat: String - ) { - self.type = type - self.chat = chat - } -} - -public struct SessionChatUpdatedAction: Codable, Sendable { - public var type: ActionType - /// The URI of the chat whose summary changed. - public var chat: String - /// Mutable summary fields that changed; omitted fields are unchanged. - /// - /// Identity fields (`resource`) never change and MUST be omitted by - /// senders; receivers SHOULD ignore them if present. - public var changes: PartialChatSummary - - public init( - type: ActionType, - chat: String, - changes: PartialChatSummary - ) { - self.type = type - self.chat = chat - self.changes = changes - } -} - -public struct SessionDefaultChatChangedAction: Codable, Sendable { - public var type: ActionType - /// New default chat URI, or `undefined` to clear the hint. - public var defaultChat: String? - - public init( - type: ActionType, - defaultChat: String? = nil - ) { - self.type = type - self.defaultChat = defaultChat - } -} - -public struct ChatTurnStartedAction: Codable, Sendable { +public struct SessionTurnStartedAction: Codable, Sendable { public var type: ActionType /// Turn identifier public var turnId: String @@ -257,7 +190,7 @@ public struct ChatTurnStartedAction: Codable, Sendable { } } -public struct ChatDeltaAction: Codable, Sendable { +public struct SessionDeltaAction: Codable, Sendable { public var type: ActionType /// Turn identifier public var turnId: String @@ -279,7 +212,7 @@ public struct ChatDeltaAction: Codable, Sendable { } } -public struct ChatResponsePartAction: Codable, Sendable { +public struct SessionResponsePartAction: Codable, Sendable { public var type: ActionType /// Turn identifier public var turnId: String @@ -297,7 +230,7 @@ public struct ChatResponsePartAction: Codable, Sendable { } } -public struct ChatToolCallStartAction: Codable, Sendable { +public struct SessionToolCallStartAction: Codable, Sendable { /// Turn identifier public var turnId: String /// Tool call identifier @@ -347,7 +280,7 @@ public struct ChatToolCallStartAction: Codable, Sendable { } } -public struct ChatToolCallDeltaAction: Codable, Sendable { +public struct SessionToolCallDeltaAction: Codable, Sendable { /// Turn identifier public var turnId: String /// Tool call identifier @@ -391,7 +324,7 @@ public struct ChatToolCallDeltaAction: Codable, Sendable { } } -public struct ChatToolCallReadyAction: Codable, Sendable { +public struct SessionToolCallReadyAction: Codable, Sendable { /// Turn identifier public var turnId: String /// Tool call identifier @@ -464,7 +397,7 @@ public struct ChatToolCallReadyAction: Codable, Sendable { } /// Client approves or denies a pending tool call (merged approved + denied variants). -public struct ChatToolCallConfirmedAction: Codable, Sendable { +public struct SessionToolCallConfirmedAction: Codable, Sendable { /// Action type discriminant public var type: String /// Turn identifier @@ -494,7 +427,7 @@ public struct ChatToolCallConfirmedAction: Codable, Sendable { } public init( - type: String = "chat/toolCallConfirmed", + type: String = "session/toolCallConfirmed", turnId: String, toolCallId: String, approved: Bool, @@ -520,7 +453,7 @@ public struct ChatToolCallConfirmedAction: Codable, Sendable { } } -public struct ChatToolCallCompleteAction: Codable, Sendable { +public struct SessionToolCallCompleteAction: Codable, Sendable { /// Turn identifier public var turnId: String /// Tool call identifier @@ -564,7 +497,7 @@ public struct ChatToolCallCompleteAction: Codable, Sendable { } } -public struct ChatToolCallResultConfirmedAction: Codable, Sendable { +public struct SessionToolCallResultConfirmedAction: Codable, Sendable { /// Turn identifier public var turnId: String /// Tool call identifier @@ -603,46 +536,7 @@ public struct ChatToolCallResultConfirmedAction: Codable, Sendable { } } -public struct ChatToolCallContentChangedAction: Codable, Sendable { - /// Turn identifier - public var turnId: String - /// Tool call identifier - public var toolCallId: String - /// Additional provider-specific metadata for this tool call. - /// - /// Clients MAY look for well-known keys here to provide enhanced UI. - /// For example, a `ptyTerminal` key with `{ input: string; output: string }` - /// indicates the tool operated on a terminal (both `input` and `output` may - /// contain escape sequences). - public var meta: [String: AnyCodable]? - public var type: ActionType - /// The current partial content for the running tool call - public var content: [ToolResultContent] - - enum CodingKeys: String, CodingKey { - case turnId - case toolCallId - case meta = "_meta" - case type - case content - } - - public init( - turnId: String, - toolCallId: String, - meta: [String: AnyCodable]? = nil, - type: ActionType, - content: [ToolResultContent] - ) { - self.turnId = turnId - self.toolCallId = toolCallId - self.meta = meta - self.type = type - self.content = content - } -} - -public struct ChatTurnCompleteAction: Codable, Sendable { +public struct SessionTurnCompleteAction: Codable, Sendable { public var type: ActionType /// Turn identifier public var turnId: String @@ -656,7 +550,7 @@ public struct ChatTurnCompleteAction: Codable, Sendable { } } -public struct ChatTurnCancelledAction: Codable, Sendable { +public struct SessionTurnCancelledAction: Codable, Sendable { public var type: ActionType /// Turn identifier public var turnId: String @@ -670,7 +564,7 @@ public struct ChatTurnCancelledAction: Codable, Sendable { } } -public struct ChatErrorAction: Codable, Sendable { +public struct SessionErrorAction: Codable, Sendable { public var type: ActionType /// Turn identifier public var turnId: String @@ -702,7 +596,7 @@ public struct SessionTitleChangedAction: Codable, Sendable { } } -public struct ChatUsageAction: Codable, Sendable { +public struct SessionUsageAction: Codable, Sendable { public var type: ActionType /// Turn identifier public var turnId: String @@ -720,7 +614,7 @@ public struct ChatUsageAction: Codable, Sendable { } } -public struct ChatReasoningAction: Codable, Sendable { +public struct SessionReasoningAction: Codable, Sendable { public var type: ActionType /// Turn identifier public var turnId: String @@ -869,7 +763,7 @@ public struct SessionActiveClientToolsChangedAction: Codable, Sendable { } } -public struct ChatPendingMessageSetAction: Codable, Sendable { +public struct SessionPendingMessageSetAction: Codable, Sendable { public var type: ActionType /// Whether this is a steering or queued message public var kind: PendingMessageKind @@ -891,7 +785,7 @@ public struct ChatPendingMessageSetAction: Codable, Sendable { } } -public struct ChatPendingMessageRemovedAction: Codable, Sendable { +public struct SessionPendingMessageRemovedAction: Codable, Sendable { public var type: ActionType /// Whether this is a steering or queued message public var kind: PendingMessageKind @@ -909,7 +803,7 @@ public struct ChatPendingMessageRemovedAction: Codable, Sendable { } } -public struct ChatQueuedMessagesReorderedAction: Codable, Sendable { +public struct SessionQueuedMessagesReorderedAction: Codable, Sendable { public var type: ActionType /// Queued message IDs in the desired order public var order: [String] @@ -923,34 +817,34 @@ public struct ChatQueuedMessagesReorderedAction: Codable, Sendable { } } -public struct ChatInputRequestedAction: Codable, Sendable { +public struct SessionInputRequestedAction: Codable, Sendable { public var type: ActionType /// Input request to create or replace - public var request: ChatInputRequest + public var request: SessionInputRequest public init( type: ActionType, - request: ChatInputRequest + request: SessionInputRequest ) { self.type = type self.request = request } } -public struct ChatInputAnswerChangedAction: Codable, Sendable { +public struct SessionInputAnswerChangedAction: Codable, Sendable { public var type: ActionType /// Input request identifier public var requestId: String /// Question identifier within the input request public var questionId: String /// Updated answer, or `undefined` to clear an answer draft - public var answer: ChatInputAnswer? + public var answer: SessionInputAnswer? public init( type: ActionType, requestId: String, questionId: String, - answer: ChatInputAnswer? = nil + answer: SessionInputAnswer? = nil ) { self.type = type self.requestId = requestId @@ -959,20 +853,20 @@ public struct ChatInputAnswerChangedAction: Codable, Sendable { } } -public struct ChatInputCompletedAction: Codable, Sendable { +public struct SessionInputCompletedAction: Codable, Sendable { public var type: ActionType /// Input request identifier public var requestId: String /// Completion outcome - public var response: ChatInputResponseKind + public var response: SessionInputResponseKind /// Optional final answer replacement, keyed by question ID - public var answers: [String: ChatInputAnswer]? + public var answers: [String: SessionInputAnswer]? public init( type: ActionType, requestId: String, - response: ChatInputResponseKind, - answers: [String: ChatInputAnswer]? = nil + response: SessionInputResponseKind, + answers: [String: SessionInputAnswer]? = nil ) { self.type = type self.requestId = requestId @@ -1065,7 +959,7 @@ public struct SessionMcpServerStateChangedAction: Codable, Sendable { } } -public struct ChatTruncatedAction: Codable, Sendable { +public struct SessionTruncatedAction: Codable, Sendable { public var type: ActionType /// Keep turns up to and including this turn. Omit to clear all turns. public var turnId: String? @@ -1116,6 +1010,45 @@ public struct SessionMetaChangedAction: Codable, Sendable { } } +public struct SessionToolCallContentChangedAction: Codable, Sendable { + /// Turn identifier + public var turnId: String + /// Tool call identifier + public var toolCallId: String + /// Additional provider-specific metadata for this tool call. + /// + /// Clients MAY look for well-known keys here to provide enhanced UI. + /// For example, a `ptyTerminal` key with `{ input: string; output: string }` + /// indicates the tool operated on a terminal (both `input` and `output` may + /// contain escape sequences). + public var meta: [String: AnyCodable]? + public var type: ActionType + /// The current partial content for the running tool call + public var content: [ToolResultContent] + + enum CodingKeys: String, CodingKey { + case turnId + case toolCallId + case meta = "_meta" + case type + case content + } + + public init( + turnId: String, + toolCallId: String, + meta: [String: AnyCodable]? = nil, + type: ActionType, + content: [ToolResultContent] + ) { + self.turnId = turnId + self.toolCallId = toolCallId + self.meta = meta + self.type = type + self.content = content + } +} + public struct ChangesetStatusChangedAction: Codable, Sendable { public var type: ActionType /// New computation lifecycle status. @@ -1524,55 +1457,6 @@ public struct ResourceWatchChangedAction: Codable, Sendable { } } -// MARK: - Partial Summary Types - -public struct PartialChatSummary: Codable, Sendable { - /// Chat URI - public var resource: String? - /// Chat title - public var title: String? - /// Current chat status (reuses SessionStatus shape) - public var status: SessionStatus? - /// Human-readable description of what the chat is currently doing - public var activity: String? - /// Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - public var modifiedAt: String? - /// Optional per-chat model override (defaults to the session's model) - public var model: ModelSelection? - /// Optional per-chat agent override (defaults to the session's agent) - public var agent: AgentSelection? - /// How this chat came into existence - public var origin: ChatOrigin? - /// Optional per-chat working directory. - /// - /// If absent, the chat inherits - /// {@link SessionSummary.workingDirectory | the session's working directory}. - /// See {@link ChatState.workingDirectory} for usage notes. - public var workingDirectory: String? - - public init( - resource: String? = nil, - title: String? = nil, - status: SessionStatus? = nil, - activity: String? = nil, - modifiedAt: String? = nil, - model: ModelSelection? = nil, - agent: AgentSelection? = nil, - origin: ChatOrigin? = nil, - workingDirectory: String? = nil - ) { - self.resource = resource - self.title = title - self.status = status - self.activity = activity - self.modifiedAt = modifiedAt - self.model = model - self.agent = agent - self.origin = origin - self.workingDirectory = workingDirectory - } -} - // MARK: - StateAction Union /// Discriminated union of all state actions. @@ -1581,26 +1465,21 @@ public enum StateAction: Codable, Sendable { case rootActiveSessionsChanged(RootActiveSessionsChangedAction) case sessionReady(SessionReadyAction) case sessionCreationFailed(SessionCreationFailedAction) - case sessionChatAdded(SessionChatAddedAction) - case sessionChatRemoved(SessionChatRemovedAction) - case sessionChatUpdated(SessionChatUpdatedAction) - case sessionDefaultChatChanged(SessionDefaultChatChangedAction) - case chatTurnStarted(ChatTurnStartedAction) - case chatDelta(ChatDeltaAction) - case chatResponsePart(ChatResponsePartAction) - case chatToolCallStart(ChatToolCallStartAction) - case chatToolCallDelta(ChatToolCallDeltaAction) - case chatToolCallReady(ChatToolCallReadyAction) - case chatToolCallConfirmed(ChatToolCallConfirmedAction) - case chatToolCallComplete(ChatToolCallCompleteAction) - case chatToolCallResultConfirmed(ChatToolCallResultConfirmedAction) - case chatToolCallContentChanged(ChatToolCallContentChangedAction) - case chatTurnComplete(ChatTurnCompleteAction) - case chatTurnCancelled(ChatTurnCancelledAction) - case chatError(ChatErrorAction) + case sessionTurnStarted(SessionTurnStartedAction) + case sessionDelta(SessionDeltaAction) + case sessionResponsePart(SessionResponsePartAction) + case sessionToolCallStart(SessionToolCallStartAction) + case sessionToolCallDelta(SessionToolCallDeltaAction) + case sessionToolCallReady(SessionToolCallReadyAction) + case sessionToolCallConfirmed(SessionToolCallConfirmedAction) + case sessionToolCallComplete(SessionToolCallCompleteAction) + case sessionToolCallResultConfirmed(SessionToolCallResultConfirmedAction) + case sessionTurnComplete(SessionTurnCompleteAction) + case sessionTurnCancelled(SessionTurnCancelledAction) + case sessionError(SessionErrorAction) case sessionTitleChanged(SessionTitleChangedAction) - case chatUsage(ChatUsageAction) - case chatReasoning(ChatReasoningAction) + case sessionUsage(SessionUsageAction) + case sessionReasoning(SessionReasoningAction) case sessionModelChanged(SessionModelChangedAction) case sessionAgentChanged(SessionAgentChangedAction) case sessionIsReadChanged(SessionIsReadChangedAction) @@ -1610,20 +1489,21 @@ public enum StateAction: Codable, Sendable { case sessionServerToolsChanged(SessionServerToolsChangedAction) case sessionActiveClientChanged(SessionActiveClientChangedAction) case sessionActiveClientToolsChanged(SessionActiveClientToolsChangedAction) - case chatPendingMessageSet(ChatPendingMessageSetAction) - case chatPendingMessageRemoved(ChatPendingMessageRemovedAction) - case chatQueuedMessagesReordered(ChatQueuedMessagesReorderedAction) - case chatInputRequested(ChatInputRequestedAction) - case chatInputAnswerChanged(ChatInputAnswerChangedAction) - case chatInputCompleted(ChatInputCompletedAction) + case sessionPendingMessageSet(SessionPendingMessageSetAction) + case sessionPendingMessageRemoved(SessionPendingMessageRemovedAction) + case sessionQueuedMessagesReordered(SessionQueuedMessagesReorderedAction) + case sessionInputRequested(SessionInputRequestedAction) + case sessionInputAnswerChanged(SessionInputAnswerChangedAction) + case sessionInputCompleted(SessionInputCompletedAction) case sessionCustomizationsChanged(SessionCustomizationsChangedAction) case sessionCustomizationToggled(SessionCustomizationToggledAction) case sessionCustomizationUpdated(SessionCustomizationUpdatedAction) case sessionCustomizationRemoved(SessionCustomizationRemovedAction) - case sessionMcpServerStateChanged(SessionMcpServerStateChangedAction) - case chatTruncated(ChatTruncatedAction) + case sessionMcpServerStatusChanged(SessionMcpServerStateChangedAction) + case sessionTruncated(SessionTruncatedAction) case sessionConfigChanged(SessionConfigChangedAction) case sessionMetaChanged(SessionMetaChangedAction) + case sessionToolCallContentChanged(SessionToolCallContentChangedAction) case changesetStatusChanged(ChangesetStatusChangedAction) case changesetFileSet(ChangesetFileSetAction) case changesetFileRemoved(ChangesetFileRemovedAction) @@ -1666,46 +1546,36 @@ public enum StateAction: Codable, Sendable { self = .sessionReady(try SessionReadyAction(from: decoder)) case "session/creationFailed": self = .sessionCreationFailed(try SessionCreationFailedAction(from: decoder)) - case "session/chatAdded": - self = .sessionChatAdded(try SessionChatAddedAction(from: decoder)) - case "session/chatRemoved": - self = .sessionChatRemoved(try SessionChatRemovedAction(from: decoder)) - case "session/chatUpdated": - self = .sessionChatUpdated(try SessionChatUpdatedAction(from: decoder)) - case "session/defaultChatChanged": - self = .sessionDefaultChatChanged(try SessionDefaultChatChangedAction(from: decoder)) - case "chat/turnStarted": - self = .chatTurnStarted(try ChatTurnStartedAction(from: decoder)) - case "chat/delta": - self = .chatDelta(try ChatDeltaAction(from: decoder)) - case "chat/responsePart": - self = .chatResponsePart(try ChatResponsePartAction(from: decoder)) - case "chat/toolCallStart": - self = .chatToolCallStart(try ChatToolCallStartAction(from: decoder)) - case "chat/toolCallDelta": - self = .chatToolCallDelta(try ChatToolCallDeltaAction(from: decoder)) - case "chat/toolCallReady": - self = .chatToolCallReady(try ChatToolCallReadyAction(from: decoder)) - case "chat/toolCallConfirmed": - self = .chatToolCallConfirmed(try ChatToolCallConfirmedAction(from: decoder)) - case "chat/toolCallComplete": - self = .chatToolCallComplete(try ChatToolCallCompleteAction(from: decoder)) - case "chat/toolCallResultConfirmed": - self = .chatToolCallResultConfirmed(try ChatToolCallResultConfirmedAction(from: decoder)) - case "chat/toolCallContentChanged": - self = .chatToolCallContentChanged(try ChatToolCallContentChangedAction(from: decoder)) - case "chat/turnComplete": - self = .chatTurnComplete(try ChatTurnCompleteAction(from: decoder)) - case "chat/turnCancelled": - self = .chatTurnCancelled(try ChatTurnCancelledAction(from: decoder)) - case "chat/error": - self = .chatError(try ChatErrorAction(from: decoder)) + case "session/turnStarted": + self = .sessionTurnStarted(try SessionTurnStartedAction(from: decoder)) + case "session/delta": + self = .sessionDelta(try SessionDeltaAction(from: decoder)) + case "session/responsePart": + self = .sessionResponsePart(try SessionResponsePartAction(from: decoder)) + case "session/toolCallStart": + self = .sessionToolCallStart(try SessionToolCallStartAction(from: decoder)) + case "session/toolCallDelta": + self = .sessionToolCallDelta(try SessionToolCallDeltaAction(from: decoder)) + case "session/toolCallReady": + self = .sessionToolCallReady(try SessionToolCallReadyAction(from: decoder)) + case "session/toolCallConfirmed": + self = .sessionToolCallConfirmed(try SessionToolCallConfirmedAction(from: decoder)) + case "session/toolCallComplete": + self = .sessionToolCallComplete(try SessionToolCallCompleteAction(from: decoder)) + case "session/toolCallResultConfirmed": + self = .sessionToolCallResultConfirmed(try SessionToolCallResultConfirmedAction(from: decoder)) + case "session/turnComplete": + self = .sessionTurnComplete(try SessionTurnCompleteAction(from: decoder)) + case "session/turnCancelled": + self = .sessionTurnCancelled(try SessionTurnCancelledAction(from: decoder)) + case "session/error": + self = .sessionError(try SessionErrorAction(from: decoder)) case "session/titleChanged": self = .sessionTitleChanged(try SessionTitleChangedAction(from: decoder)) - case "chat/usage": - self = .chatUsage(try ChatUsageAction(from: decoder)) - case "chat/reasoning": - self = .chatReasoning(try ChatReasoningAction(from: decoder)) + case "session/usage": + self = .sessionUsage(try SessionUsageAction(from: decoder)) + case "session/reasoning": + self = .sessionReasoning(try SessionReasoningAction(from: decoder)) case "session/modelChanged": self = .sessionModelChanged(try SessionModelChangedAction(from: decoder)) case "session/agentChanged": @@ -1724,18 +1594,18 @@ public enum StateAction: Codable, Sendable { self = .sessionActiveClientChanged(try SessionActiveClientChangedAction(from: decoder)) case "session/activeClientToolsChanged": self = .sessionActiveClientToolsChanged(try SessionActiveClientToolsChangedAction(from: decoder)) - case "chat/pendingMessageSet": - self = .chatPendingMessageSet(try ChatPendingMessageSetAction(from: decoder)) - case "chat/pendingMessageRemoved": - self = .chatPendingMessageRemoved(try ChatPendingMessageRemovedAction(from: decoder)) - case "chat/queuedMessagesReordered": - self = .chatQueuedMessagesReordered(try ChatQueuedMessagesReorderedAction(from: decoder)) - case "chat/inputRequested": - self = .chatInputRequested(try ChatInputRequestedAction(from: decoder)) - case "chat/inputAnswerChanged": - self = .chatInputAnswerChanged(try ChatInputAnswerChangedAction(from: decoder)) - case "chat/inputCompleted": - self = .chatInputCompleted(try ChatInputCompletedAction(from: decoder)) + case "session/pendingMessageSet": + self = .sessionPendingMessageSet(try SessionPendingMessageSetAction(from: decoder)) + case "session/pendingMessageRemoved": + self = .sessionPendingMessageRemoved(try SessionPendingMessageRemovedAction(from: decoder)) + case "session/queuedMessagesReordered": + self = .sessionQueuedMessagesReordered(try SessionQueuedMessagesReorderedAction(from: decoder)) + case "session/inputRequested": + self = .sessionInputRequested(try SessionInputRequestedAction(from: decoder)) + case "session/inputAnswerChanged": + self = .sessionInputAnswerChanged(try SessionInputAnswerChangedAction(from: decoder)) + case "session/inputCompleted": + self = .sessionInputCompleted(try SessionInputCompletedAction(from: decoder)) case "session/customizationsChanged": self = .sessionCustomizationsChanged(try SessionCustomizationsChangedAction(from: decoder)) case "session/customizationToggled": @@ -1745,13 +1615,15 @@ public enum StateAction: Codable, Sendable { case "session/customizationRemoved": self = .sessionCustomizationRemoved(try SessionCustomizationRemovedAction(from: decoder)) case "session/mcpServerStateChanged": - self = .sessionMcpServerStateChanged(try SessionMcpServerStateChangedAction(from: decoder)) - case "chat/truncated": - self = .chatTruncated(try ChatTruncatedAction(from: decoder)) + self = .sessionMcpServerStatusChanged(try SessionMcpServerStateChangedAction(from: decoder)) + case "session/truncated": + self = .sessionTruncated(try SessionTruncatedAction(from: decoder)) case "session/configChanged": self = .sessionConfigChanged(try SessionConfigChangedAction(from: decoder)) case "session/metaChanged": self = .sessionMetaChanged(try SessionMetaChangedAction(from: decoder)) + case "session/toolCallContentChanged": + self = .sessionToolCallContentChanged(try SessionToolCallContentChangedAction(from: decoder)) case "changeset/statusChanged": self = .changesetStatusChanged(try ChangesetStatusChangedAction(from: decoder)) case "changeset/fileSet": @@ -1813,26 +1685,21 @@ public enum StateAction: Codable, Sendable { case .rootActiveSessionsChanged(let v): try v.encode(to: encoder) case .sessionReady(let v): try v.encode(to: encoder) case .sessionCreationFailed(let v): try v.encode(to: encoder) - case .sessionChatAdded(let v): try v.encode(to: encoder) - case .sessionChatRemoved(let v): try v.encode(to: encoder) - case .sessionChatUpdated(let v): try v.encode(to: encoder) - case .sessionDefaultChatChanged(let v): try v.encode(to: encoder) - case .chatTurnStarted(let v): try v.encode(to: encoder) - case .chatDelta(let v): try v.encode(to: encoder) - case .chatResponsePart(let v): try v.encode(to: encoder) - case .chatToolCallStart(let v): try v.encode(to: encoder) - case .chatToolCallDelta(let v): try v.encode(to: encoder) - case .chatToolCallReady(let v): try v.encode(to: encoder) - case .chatToolCallConfirmed(let v): try v.encode(to: encoder) - case .chatToolCallComplete(let v): try v.encode(to: encoder) - case .chatToolCallResultConfirmed(let v): try v.encode(to: encoder) - case .chatToolCallContentChanged(let v): try v.encode(to: encoder) - case .chatTurnComplete(let v): try v.encode(to: encoder) - case .chatTurnCancelled(let v): try v.encode(to: encoder) - case .chatError(let v): try v.encode(to: encoder) + case .sessionTurnStarted(let v): try v.encode(to: encoder) + case .sessionDelta(let v): try v.encode(to: encoder) + case .sessionResponsePart(let v): try v.encode(to: encoder) + case .sessionToolCallStart(let v): try v.encode(to: encoder) + case .sessionToolCallDelta(let v): try v.encode(to: encoder) + case .sessionToolCallReady(let v): try v.encode(to: encoder) + case .sessionToolCallConfirmed(let v): try v.encode(to: encoder) + case .sessionToolCallComplete(let v): try v.encode(to: encoder) + case .sessionToolCallResultConfirmed(let v): try v.encode(to: encoder) + case .sessionTurnComplete(let v): try v.encode(to: encoder) + case .sessionTurnCancelled(let v): try v.encode(to: encoder) + case .sessionError(let v): try v.encode(to: encoder) case .sessionTitleChanged(let v): try v.encode(to: encoder) - case .chatUsage(let v): try v.encode(to: encoder) - case .chatReasoning(let v): try v.encode(to: encoder) + case .sessionUsage(let v): try v.encode(to: encoder) + case .sessionReasoning(let v): try v.encode(to: encoder) case .sessionModelChanged(let v): try v.encode(to: encoder) case .sessionAgentChanged(let v): try v.encode(to: encoder) case .sessionIsReadChanged(let v): try v.encode(to: encoder) @@ -1842,20 +1709,21 @@ public enum StateAction: Codable, Sendable { case .sessionServerToolsChanged(let v): try v.encode(to: encoder) case .sessionActiveClientChanged(let v): try v.encode(to: encoder) case .sessionActiveClientToolsChanged(let v): try v.encode(to: encoder) - case .chatPendingMessageSet(let v): try v.encode(to: encoder) - case .chatPendingMessageRemoved(let v): try v.encode(to: encoder) - case .chatQueuedMessagesReordered(let v): try v.encode(to: encoder) - case .chatInputRequested(let v): try v.encode(to: encoder) - case .chatInputAnswerChanged(let v): try v.encode(to: encoder) - case .chatInputCompleted(let v): try v.encode(to: encoder) + case .sessionPendingMessageSet(let v): try v.encode(to: encoder) + case .sessionPendingMessageRemoved(let v): try v.encode(to: encoder) + case .sessionQueuedMessagesReordered(let v): try v.encode(to: encoder) + case .sessionInputRequested(let v): try v.encode(to: encoder) + case .sessionInputAnswerChanged(let v): try v.encode(to: encoder) + case .sessionInputCompleted(let v): try v.encode(to: encoder) case .sessionCustomizationsChanged(let v): try v.encode(to: encoder) case .sessionCustomizationToggled(let v): try v.encode(to: encoder) case .sessionCustomizationUpdated(let v): try v.encode(to: encoder) case .sessionCustomizationRemoved(let v): try v.encode(to: encoder) - case .sessionMcpServerStateChanged(let v): try v.encode(to: encoder) - case .chatTruncated(let v): try v.encode(to: encoder) + case .sessionMcpServerStatusChanged(let v): try v.encode(to: encoder) + case .sessionTruncated(let v): try v.encode(to: encoder) case .sessionConfigChanged(let v): try v.encode(to: encoder) case .sessionMetaChanged(let v): try v.encode(to: encoder) + case .sessionToolCallContentChanged(let v): try v.encode(to: encoder) case .changesetStatusChanged(let v): try v.encode(to: encoder) case .changesetFileSet(let v): try v.encode(to: encoder) case .changesetFileRemoved(let v): try v.encode(to: encoder) diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift index f03d08f0..922d1e6f 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Commands.generated.swift @@ -318,63 +318,6 @@ public struct DisposeSessionParams: Codable, Sendable { } } -public struct ChatForkSource: Codable, Sendable { - /// URI of the existing chat to fork from - public var chat: String - /// Turn ID in the source chat; content up to and including this turn's response is copied - public var turnId: String - - public init( - chat: String, - turnId: String - ) { - self.chat = chat - self.turnId = turnId - } -} - -public struct CreateChatParams: Codable, Sendable { - /// Channel URI this command targets. - public var channel: String - /// Chat URI (client-chosen, e.g. `ahp-chat:/`). - public var chat: String - /// Optional initial message for the new chat. - public var initialMessage: Message? - /// Optional per-chat model override. - public var model: ModelSelection? - /// Optional per-chat agent override. - public var agent: AgentSelection? - /// Optional source chat and turn to fork from. - public var source: ChatForkSource? - - public init( - channel: String, - chat: String, - initialMessage: Message? = nil, - model: ModelSelection? = nil, - agent: AgentSelection? = nil, - source: ChatForkSource? = nil - ) { - self.channel = channel - self.chat = chat - self.initialMessage = initialMessage - self.model = model - self.agent = agent - self.source = source - } -} - -public struct DisposeChatParams: Codable, Sendable { - /// Channel URI this command targets. - public var channel: String - - public init( - channel: String - ) { - self.channel = channel - } -} - public struct ListSessionsParams: Codable, Sendable { /// Channel URI this command targets. public var channel: String diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift index ead87b1d..739e0234 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/Notifications.generated.swift @@ -162,10 +162,7 @@ public struct PartialSessionSummary: Codable, Sendable { /// Absent (`undefined`) means no custom agent is selected for this session /// — the session uses the provider's default behavior. public var agent: AgentSelection? - /// The default working directory URI for this session. Individual chats - /// MAY override via {@link ChatSummary.workingDirectory | their own - /// `workingDirectory`}; this field acts as the fallback for any chat that - /// does not. + /// The working directory URI for this session public var workingDirectory: String? /// Aggregate summary of file changes associated with this session. Servers /// may populate this to give clients a quick at-a-glance view of the diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index 94dfcfb7..ba335d04 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -85,21 +85,15 @@ public struct SessionStatus: OptionSet, Codable, Sendable, Hashable { public static let isArchived = SessionStatus(rawValue: 64) } -public enum ChatOriginKind: String, Codable, Sendable { - case user = "user" - case fork = "fork" - case tool = "tool" -} - /// Answer lifecycle state. -public enum ChatInputAnswerState: String, Codable, Sendable { +public enum SessionInputAnswerState: String, Codable, Sendable { case draft = "draft" case submitted = "submitted" case skipped = "skipped" } /// Answer value kind. -public enum ChatInputAnswerValueKind: String, Codable, Sendable { +public enum SessionInputAnswerValueKind: String, Codable, Sendable { case text = "text" case number = "number" case boolean = "boolean" @@ -108,7 +102,7 @@ public enum ChatInputAnswerValueKind: String, Codable, Sendable { } /// Question/input control kind. -public enum ChatInputQuestionKind: String, Codable, Sendable { +public enum SessionInputQuestionKind: String, Codable, Sendable { case text = "text" case number = "number" case integer = "integer" @@ -118,7 +112,7 @@ public enum ChatInputQuestionKind: String, Codable, Sendable { } /// How a client completed an input request. -public enum ChatInputResponseKind: String, Codable, Sendable { +public enum SessionInputResponseKind: String, Codable, Sendable { case accept = "accept" case decline = "decline" case cancel = "cancel" @@ -728,144 +722,6 @@ public struct PendingMessage: Codable, Sendable { } } -public struct ChatState: Codable, Sendable { - /// Chat URI - public var resource: String - /// Chat title - public var title: String - /// Current chat status (reuses SessionStatus shape) - public var status: SessionStatus - /// Human-readable description of what the chat is currently doing - public var activity: String? - /// Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - public var modifiedAt: String - /// Optional per-chat model override (defaults to the session's model) - public var model: ModelSelection? - /// Optional per-chat agent override (defaults to the session's agent) - public var agent: AgentSelection? - /// How this chat came into existence - public var origin: ChatOrigin? - /// Optional per-chat working directory. - /// - /// If absent, the chat inherits - /// {@link SessionSummary.workingDirectory | the session's working directory}. - /// Hosts MAY override this for individual chats — for example, to give a - /// subordinate chat its own git worktree so multiple chats in a session can - /// make independent edits that the orchestrator later merges back. - public var workingDirectory: String? - /// Completed turns - public var turns: [Turn] - /// Currently in-progress turn - public var activeTurn: ActiveTurn? - /// Message to inject into the current turn at a convenient point - public var steeringMessage: PendingMessage? - /// Messages to send automatically as new turns after the current turn finishes - public var queuedMessages: [PendingMessage]? - /// Requests for user input that are currently blocking or informing chat progress - public var inputRequests: [ChatInputRequest]? - /// Additional provider-specific metadata for this chat. - public var meta: [String: AnyCodable]? - - enum CodingKeys: String, CodingKey { - case resource - case title - case status - case activity - case modifiedAt - case model - case agent - case origin - case workingDirectory - case turns - case activeTurn - case steeringMessage - case queuedMessages - case inputRequests - case meta = "_meta" - } - - public init( - resource: String, - title: String, - status: SessionStatus, - activity: String? = nil, - modifiedAt: String, - model: ModelSelection? = nil, - agent: AgentSelection? = nil, - origin: ChatOrigin? = nil, - workingDirectory: String? = nil, - turns: [Turn], - activeTurn: ActiveTurn? = nil, - steeringMessage: PendingMessage? = nil, - queuedMessages: [PendingMessage]? = nil, - inputRequests: [ChatInputRequest]? = nil, - meta: [String: AnyCodable]? = nil - ) { - self.resource = resource - self.title = title - self.status = status - self.activity = activity - self.modifiedAt = modifiedAt - self.model = model - self.agent = agent - self.origin = origin - self.workingDirectory = workingDirectory - self.turns = turns - self.activeTurn = activeTurn - self.steeringMessage = steeringMessage - self.queuedMessages = queuedMessages - self.inputRequests = inputRequests - self.meta = meta - } -} - -public struct ChatSummary: Codable, Sendable { - /// Chat URI - public var resource: String - /// Chat title - public var title: String - /// Current chat status (reuses SessionStatus shape) - public var status: SessionStatus - /// Human-readable description of what the chat is currently doing - public var activity: String? - /// Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) - public var modifiedAt: String - /// Optional per-chat model override (defaults to the session's model) - public var model: ModelSelection? - /// Optional per-chat agent override (defaults to the session's agent) - public var agent: AgentSelection? - /// How this chat came into existence - public var origin: ChatOrigin? - /// Optional per-chat working directory. - /// - /// If absent, the chat inherits - /// {@link SessionSummary.workingDirectory | the session's working directory}. - /// See {@link ChatState.workingDirectory} for usage notes. - public var workingDirectory: String? - - public init( - resource: String, - title: String, - status: SessionStatus, - activity: String? = nil, - modifiedAt: String, - model: ModelSelection? = nil, - agent: AgentSelection? = nil, - origin: ChatOrigin? = nil, - workingDirectory: String? = nil - ) { - self.resource = resource - self.title = title - self.status = status - self.activity = activity - self.modifiedAt = modifiedAt - self.model = model - self.agent = agent - self.origin = origin - self.workingDirectory = workingDirectory - } -} - public struct SessionState: Codable, Sendable { /// Lightweight session metadata public var summary: SessionSummary @@ -877,13 +733,16 @@ public struct SessionState: Codable, Sendable { public var serverTools: [ToolDefinition]? /// The client currently providing tools and interactive capabilities to this session public var activeClient: SessionActiveClient? - /// Catalog of chats in this session. - public var chats: [ChatSummary] - /// The chat that receives input when the user addresses the session without - /// selecting a specific chat. This is a UI routing hint, not a hierarchy - /// marker — chats remain equal peers at the protocol level. Hosts MAY change - /// this over the session's lifetime. - public var defaultChat: String? + /// Completed turns + public var turns: [Turn] + /// Currently in-progress turn + public var activeTurn: ActiveTurn? + /// Message to inject into the current turn at a convenient point + public var steeringMessage: PendingMessage? + /// Messages to send automatically as new turns after the current turn finishes + public var queuedMessages: [PendingMessage]? + /// Requests for user input that are currently blocking or informing session progress + public var inputRequests: [SessionInputRequest]? /// Session configuration schema and current values public var config: SessionConfigState? /// Top-level customizations active in this session. @@ -925,8 +784,11 @@ public struct SessionState: Codable, Sendable { case creationError case serverTools case activeClient - case chats - case defaultChat + case turns + case activeTurn + case steeringMessage + case queuedMessages + case inputRequests case config case customizations case changesets @@ -939,8 +801,11 @@ public struct SessionState: Codable, Sendable { creationError: ErrorInfo? = nil, serverTools: [ToolDefinition]? = nil, activeClient: SessionActiveClient? = nil, - chats: [ChatSummary], - defaultChat: String? = nil, + turns: [Turn], + activeTurn: ActiveTurn? = nil, + steeringMessage: PendingMessage? = nil, + queuedMessages: [PendingMessage]? = nil, + inputRequests: [SessionInputRequest]? = nil, config: SessionConfigState? = nil, customizations: [Customization]? = nil, changesets: [Changeset]? = nil, @@ -951,8 +816,11 @@ public struct SessionState: Codable, Sendable { self.creationError = creationError self.serverTools = serverTools self.activeClient = activeClient - self.chats = chats - self.defaultChat = defaultChat + self.turns = turns + self.activeTurn = activeTurn + self.steeringMessage = steeringMessage + self.queuedMessages = queuedMessages + self.inputRequests = inputRequests self.config = config self.customizations = customizations self.changesets = changesets @@ -1012,10 +880,7 @@ public struct SessionSummary: Codable, Sendable { /// Absent (`undefined`) means no custom agent is selected for this session /// — the session uses the provider's default behavior. public var agent: AgentSelection? - /// The default working directory URI for this session. Individual chats - /// MAY override via {@link ChatSummary.workingDirectory | their own - /// `workingDirectory`}; this field acts as the fallback for any chat that - /// does not. + /// The working directory URI for this session public var workingDirectory: String? /// Aggregate summary of file changes associated with this session. Servers /// may populate this to give clients a quick at-a-glance view of the @@ -1201,7 +1066,7 @@ public struct Message: Codable, Sendable { } } -public struct ChatInputOption: Codable, Sendable { +public struct SessionInputOption: Codable, Sendable { /// Stable option identifier; for MCP enum values this is the enum string public var id: String /// Display label @@ -1224,12 +1089,12 @@ public struct ChatInputOption: Codable, Sendable { } } -public struct ChatInputTextAnswerValue: Codable, Sendable { - public var kind: ChatInputAnswerValueKind +public struct SessionInputTextAnswerValue: Codable, Sendable { + public var kind: SessionInputAnswerValueKind public var value: String public init( - kind: ChatInputAnswerValueKind, + kind: SessionInputAnswerValueKind, value: String ) { self.kind = kind @@ -1237,12 +1102,12 @@ public struct ChatInputTextAnswerValue: Codable, Sendable { } } -public struct ChatInputNumberAnswerValue: Codable, Sendable { - public var kind: ChatInputAnswerValueKind +public struct SessionInputNumberAnswerValue: Codable, Sendable { + public var kind: SessionInputAnswerValueKind public var value: Double public init( - kind: ChatInputAnswerValueKind, + kind: SessionInputAnswerValueKind, value: Double ) { self.kind = kind @@ -1250,12 +1115,12 @@ public struct ChatInputNumberAnswerValue: Codable, Sendable { } } -public struct ChatInputBooleanAnswerValue: Codable, Sendable { - public var kind: ChatInputAnswerValueKind +public struct SessionInputBooleanAnswerValue: Codable, Sendable { + public var kind: SessionInputAnswerValueKind public var value: Bool public init( - kind: ChatInputAnswerValueKind, + kind: SessionInputAnswerValueKind, value: Bool ) { self.kind = kind @@ -1263,14 +1128,14 @@ public struct ChatInputBooleanAnswerValue: Codable, Sendable { } } -public struct ChatInputSelectedAnswerValue: Codable, Sendable { - public var kind: ChatInputAnswerValueKind +public struct SessionInputSelectedAnswerValue: Codable, Sendable { + public var kind: SessionInputAnswerValueKind public var value: String /// Free-form text entered instead of selecting an option public var freeformValues: [String]? public init( - kind: ChatInputAnswerValueKind, + kind: SessionInputAnswerValueKind, value: String, freeformValues: [String]? = nil ) { @@ -1280,14 +1145,14 @@ public struct ChatInputSelectedAnswerValue: Codable, Sendable { } } -public struct ChatInputSelectedManyAnswerValue: Codable, Sendable { - public var kind: ChatInputAnswerValueKind +public struct SessionInputSelectedManyAnswerValue: Codable, Sendable { + public var kind: SessionInputAnswerValueKind public var value: [String] /// Free-form text entered in addition to selected options public var freeformValues: [String]? public init( - kind: ChatInputAnswerValueKind, + kind: SessionInputAnswerValueKind, value: [String], freeformValues: [String]? = nil ) { @@ -1297,29 +1162,29 @@ public struct ChatInputSelectedManyAnswerValue: Codable, Sendable { } } -public struct ChatInputAnswered: Codable, Sendable { +public struct SessionInputAnswered: Codable, Sendable { /// Answer state - public var state: ChatInputAnswerState + public var state: SessionInputAnswerState /// Answer value - public var value: ChatInputAnswerValue + public var value: SessionInputAnswerValue public init( - state: ChatInputAnswerState, - value: ChatInputAnswerValue + state: SessionInputAnswerState, + value: SessionInputAnswerValue ) { self.state = state self.value = value } } -public struct ChatInputSkipped: Codable, Sendable { +public struct SessionInputSkipped: Codable, Sendable { /// Answer state - public var state: ChatInputAnswerState + public var state: SessionInputAnswerState /// Free-form reason or value captured while skipping, if any public var freeformValues: [String]? public init( - state: ChatInputAnswerState, + state: SessionInputAnswerState, freeformValues: [String]? = nil ) { self.state = state @@ -1327,7 +1192,7 @@ public struct ChatInputSkipped: Codable, Sendable { } } -public struct ChatInputTextQuestion: Codable, Sendable { +public struct SessionInputTextQuestion: Codable, Sendable { /// Stable question identifier used as the key in `answers` public var id: String /// Short display title @@ -1336,7 +1201,7 @@ public struct ChatInputTextQuestion: Codable, Sendable { public var message: String /// Whether the user must answer this question to accept the request public var required: Bool? - public var kind: ChatInputQuestionKind + public var kind: SessionInputQuestionKind /// Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` public var format: String? /// Minimum string length @@ -1351,7 +1216,7 @@ public struct ChatInputTextQuestion: Codable, Sendable { title: String? = nil, message: String, required: Bool? = nil, - kind: ChatInputQuestionKind, + kind: SessionInputQuestionKind, format: String? = nil, min: Int? = nil, max: Int? = nil, @@ -1369,7 +1234,7 @@ public struct ChatInputTextQuestion: Codable, Sendable { } } -public struct ChatInputNumberQuestion: Codable, Sendable { +public struct SessionInputNumberQuestion: Codable, Sendable { /// Stable question identifier used as the key in `answers` public var id: String /// Short display title @@ -1378,7 +1243,7 @@ public struct ChatInputNumberQuestion: Codable, Sendable { public var message: String /// Whether the user must answer this question to accept the request public var required: Bool? - public var kind: ChatInputQuestionKind + public var kind: SessionInputQuestionKind /// Minimum value public var min: Double? /// Maximum value @@ -1391,7 +1256,7 @@ public struct ChatInputNumberQuestion: Codable, Sendable { title: String? = nil, message: String, required: Bool? = nil, - kind: ChatInputQuestionKind, + kind: SessionInputQuestionKind, min: Double? = nil, max: Double? = nil, defaultValue: Double? = nil @@ -1407,7 +1272,7 @@ public struct ChatInputNumberQuestion: Codable, Sendable { } } -public struct ChatInputBooleanQuestion: Codable, Sendable { +public struct SessionInputBooleanQuestion: Codable, Sendable { /// Stable question identifier used as the key in `answers` public var id: String /// Short display title @@ -1416,7 +1281,7 @@ public struct ChatInputBooleanQuestion: Codable, Sendable { public var message: String /// Whether the user must answer this question to accept the request public var required: Bool? - public var kind: ChatInputQuestionKind + public var kind: SessionInputQuestionKind /// Default boolean value public var defaultValue: Bool? @@ -1425,7 +1290,7 @@ public struct ChatInputBooleanQuestion: Codable, Sendable { title: String? = nil, message: String, required: Bool? = nil, - kind: ChatInputQuestionKind, + kind: SessionInputQuestionKind, defaultValue: Bool? = nil ) { self.id = id @@ -1437,7 +1302,7 @@ public struct ChatInputBooleanQuestion: Codable, Sendable { } } -public struct ChatInputSingleSelectQuestion: Codable, Sendable { +public struct SessionInputSingleSelectQuestion: Codable, Sendable { /// Stable question identifier used as the key in `answers` public var id: String /// Short display title @@ -1446,9 +1311,9 @@ public struct ChatInputSingleSelectQuestion: Codable, Sendable { public var message: String /// Whether the user must answer this question to accept the request public var required: Bool? - public var kind: ChatInputQuestionKind + public var kind: SessionInputQuestionKind /// Options the user may select from - public var options: [ChatInputOption] + public var options: [SessionInputOption] /// Whether the user may enter text instead of selecting an option public var allowFreeformInput: Bool? @@ -1457,8 +1322,8 @@ public struct ChatInputSingleSelectQuestion: Codable, Sendable { title: String? = nil, message: String, required: Bool? = nil, - kind: ChatInputQuestionKind, - options: [ChatInputOption], + kind: SessionInputQuestionKind, + options: [SessionInputOption], allowFreeformInput: Bool? = nil ) { self.id = id @@ -1471,7 +1336,7 @@ public struct ChatInputSingleSelectQuestion: Codable, Sendable { } } -public struct ChatInputMultiSelectQuestion: Codable, Sendable { +public struct SessionInputMultiSelectQuestion: Codable, Sendable { /// Stable question identifier used as the key in `answers` public var id: String /// Short display title @@ -1480,9 +1345,9 @@ public struct ChatInputMultiSelectQuestion: Codable, Sendable { public var message: String /// Whether the user must answer this question to accept the request public var required: Bool? - public var kind: ChatInputQuestionKind + public var kind: SessionInputQuestionKind /// Options the user may select from - public var options: [ChatInputOption] + public var options: [SessionInputOption] /// Whether the user may enter text in addition to selecting options public var allowFreeformInput: Bool? /// Minimum selected item count @@ -1495,8 +1360,8 @@ public struct ChatInputMultiSelectQuestion: Codable, Sendable { title: String? = nil, message: String, required: Bool? = nil, - kind: ChatInputQuestionKind, - options: [ChatInputOption], + kind: SessionInputQuestionKind, + options: [SessionInputOption], allowFreeformInput: Bool? = nil, min: Int? = nil, max: Int? = nil @@ -1513,7 +1378,7 @@ public struct ChatInputMultiSelectQuestion: Codable, Sendable { } } -public struct ChatInputRequest: Codable, Sendable { +public struct SessionInputRequest: Codable, Sendable { /// Stable request identifier public var id: String /// Display message for the request as a whole @@ -1521,16 +1386,16 @@ public struct ChatInputRequest: Codable, Sendable { /// URL the user should review or open, for URL-style elicitations public var url: String? /// Ordered questions to ask the user - public var questions: [ChatInputQuestion]? + public var questions: [SessionInputQuestion]? /// Current draft or submitted answers, keyed by question ID - public var answers: [String: ChatInputAnswer]? + public var answers: [String: SessionInputAnswer]? public init( id: String, message: String? = nil, url: String? = nil, - questions: [ChatInputQuestion]? = nil, - answers: [String: ChatInputAnswer]? = nil + questions: [SessionInputQuestion]? = nil, + answers: [String: SessionInputAnswer]? = nil ) { self.id = id self.message = message @@ -1849,7 +1714,7 @@ public struct MessageAnnotationsAttachment: Codable, Sendable { public struct MarkdownResponsePart: Codable, Sendable { /// Discriminant public var kind: ResponsePartKind - /// Part identifier, used by `chat/delta` to target this part for content appends + /// Part identifier, used by `session/delta` to target this part for content appends public var id: String /// Markdown content public var content: String @@ -1925,7 +1790,7 @@ public struct ToolCallResponsePart: Codable, Sendable { public struct ReasoningResponsePart: Codable, Sendable { /// Discriminant public var kind: ResponsePartKind - /// Part identifier, used by `chat/reasoning` to target this part for content appends + /// Part identifier, used by `session/reasoning` to target this part for content appends public var id: String /// Accumulated reasoning text public var content: String @@ -3369,7 +3234,7 @@ public struct ToolCallClientContributor: Codable, Sendable { /// Absent for server-side tools. /// /// When set, the identified client is responsible for executing the tool and - /// dispatching `chat/toolCallComplete` with the result. + /// dispatching `session/toolCallComplete` with the result. public var clientId: String public init( @@ -3634,7 +3499,7 @@ public struct ErrorInfo: Codable, Sendable { } public struct Snapshot: Codable, Sendable { - /// The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`) + /// The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`) public var resource: String /// The current state of the resource public var state: SnapshotState @@ -4013,66 +3878,6 @@ public struct ResourceChange: Codable, Sendable { // MARK: - Discriminated Unions -public struct ChatOriginUser: Codable, Sendable { - public var kind: ChatOriginKind - - public init(kind: ChatOriginKind = .user) { - self.kind = kind - } -} - -public struct ChatOriginFork: Codable, Sendable { - public var kind: ChatOriginKind - public var chat: String - public var turnId: String - - public init(kind: ChatOriginKind = .fork, chat: String, turnId: String) { - self.kind = kind - self.chat = chat - self.turnId = turnId - } -} - -public struct ChatOriginTool: Codable, Sendable { - public var kind: ChatOriginKind - public var chat: String - public var toolCallId: String - - public init(kind: ChatOriginKind = .tool, chat: String, toolCallId: String) { - self.kind = kind - self.chat = chat - self.toolCallId = toolCallId - } -} - -public enum ChatOrigin: Codable, Sendable { - case user(ChatOriginUser) - case fork(ChatOriginFork) - case tool(ChatOriginTool) - - private enum DiscriminatorCodingKeys: String, CodingKey { case kind } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: DiscriminatorCodingKeys.self) - let discriminant = try container.decode(String.self, forKey: .kind) - switch discriminant { - case "user": self = .user(try ChatOriginUser(from: decoder)) - case "fork": self = .fork(try ChatOriginFork(from: decoder)) - case "tool": self = .tool(try ChatOriginTool(from: decoder)) - default: - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown ChatOrigin kind: \(discriminant)") - } - } - - public func encode(to encoder: Encoder) throws { - switch self { - case .user(let value): try value.encode(to: encoder) - case .fork(let value): try value.encode(to: encoder) - case .tool(let value): try value.encode(to: encoder) - } - } -} - public enum ResponsePart: Codable, Sendable { case markdown(MarkdownResponsePart) case contentRef(ResourceReponsePart) @@ -4217,13 +4022,13 @@ public enum TerminalContentPart: Codable, Sendable { } } -public enum ChatInputQuestion: Codable, Sendable { - case text(ChatInputTextQuestion) - case number(ChatInputNumberQuestion) - case integer(ChatInputNumberQuestion) - case boolean(ChatInputBooleanQuestion) - case singleSelect(ChatInputSingleSelectQuestion) - case multiSelect(ChatInputMultiSelectQuestion) +public enum SessionInputQuestion: Codable, Sendable { + case text(SessionInputTextQuestion) + case number(SessionInputNumberQuestion) + case integer(SessionInputNumberQuestion) + case boolean(SessionInputBooleanQuestion) + case singleSelect(SessionInputSingleSelectQuestion) + case multiSelect(SessionInputMultiSelectQuestion) private enum DiscriminantKey: String, CodingKey { case discriminant = "kind" @@ -4234,19 +4039,19 @@ public enum ChatInputQuestion: Codable, Sendable { let discriminant = try container.decode(String.self, forKey: .discriminant) switch discriminant { case "text": - self = .text(try ChatInputTextQuestion(from: decoder)) + self = .text(try SessionInputTextQuestion(from: decoder)) case "number": - self = .number(try ChatInputNumberQuestion(from: decoder)) + self = .number(try SessionInputNumberQuestion(from: decoder)) case "integer": - self = .integer(try ChatInputNumberQuestion(from: decoder)) + self = .integer(try SessionInputNumberQuestion(from: decoder)) case "boolean": - self = .boolean(try ChatInputBooleanQuestion(from: decoder)) + self = .boolean(try SessionInputBooleanQuestion(from: decoder)) case "single-select": - self = .singleSelect(try ChatInputSingleSelectQuestion(from: decoder)) + self = .singleSelect(try SessionInputSingleSelectQuestion(from: decoder)) case "multi-select": - self = .multiSelect(try ChatInputMultiSelectQuestion(from: decoder)) + self = .multiSelect(try SessionInputMultiSelectQuestion(from: decoder)) default: - throw DecodingError.dataCorruptedError(forKey: .discriminant, in: container, debugDescription: "Unknown ChatInputQuestion discriminant: \(discriminant)") + throw DecodingError.dataCorruptedError(forKey: .discriminant, in: container, debugDescription: "Unknown SessionInputQuestion discriminant: \(discriminant)") } } @@ -4262,12 +4067,12 @@ public enum ChatInputQuestion: Codable, Sendable { } } -public enum ChatInputAnswerValue: Codable, Sendable { - case text(ChatInputTextAnswerValue) - case number(ChatInputNumberAnswerValue) - case boolean(ChatInputBooleanAnswerValue) - case selected(ChatInputSelectedAnswerValue) - case selectedMany(ChatInputSelectedManyAnswerValue) +public enum SessionInputAnswerValue: Codable, Sendable { + case text(SessionInputTextAnswerValue) + case number(SessionInputNumberAnswerValue) + case boolean(SessionInputBooleanAnswerValue) + case selected(SessionInputSelectedAnswerValue) + case selectedMany(SessionInputSelectedManyAnswerValue) private enum DiscriminantKey: String, CodingKey { case discriminant = "kind" @@ -4278,17 +4083,17 @@ public enum ChatInputAnswerValue: Codable, Sendable { let discriminant = try container.decode(String.self, forKey: .discriminant) switch discriminant { case "text": - self = .text(try ChatInputTextAnswerValue(from: decoder)) + self = .text(try SessionInputTextAnswerValue(from: decoder)) case "number": - self = .number(try ChatInputNumberAnswerValue(from: decoder)) + self = .number(try SessionInputNumberAnswerValue(from: decoder)) case "boolean": - self = .boolean(try ChatInputBooleanAnswerValue(from: decoder)) + self = .boolean(try SessionInputBooleanAnswerValue(from: decoder)) case "selected": - self = .selected(try ChatInputSelectedAnswerValue(from: decoder)) + self = .selected(try SessionInputSelectedAnswerValue(from: decoder)) case "selected-many": - self = .selectedMany(try ChatInputSelectedManyAnswerValue(from: decoder)) + self = .selectedMany(try SessionInputSelectedManyAnswerValue(from: decoder)) default: - throw DecodingError.dataCorruptedError(forKey: .discriminant, in: container, debugDescription: "Unknown ChatInputAnswerValue discriminant: \(discriminant)") + throw DecodingError.dataCorruptedError(forKey: .discriminant, in: container, debugDescription: "Unknown SessionInputAnswerValue discriminant: \(discriminant)") } } @@ -4303,10 +4108,10 @@ public enum ChatInputAnswerValue: Codable, Sendable { } } -public enum ChatInputAnswer: Codable, Sendable { - case draft(ChatInputAnswered) - case submitted(ChatInputAnswered) - case skipped(ChatInputSkipped) +public enum SessionInputAnswer: Codable, Sendable { + case draft(SessionInputAnswered) + case submitted(SessionInputAnswered) + case skipped(SessionInputSkipped) private enum DiscriminantKey: String, CodingKey { case discriminant = "state" @@ -4317,13 +4122,13 @@ public enum ChatInputAnswer: Codable, Sendable { let discriminant = try container.decode(String.self, forKey: .discriminant) switch discriminant { case "draft": - self = .draft(try ChatInputAnswered(from: decoder)) + self = .draft(try SessionInputAnswered(from: decoder)) case "submitted": - self = .submitted(try ChatInputAnswered(from: decoder)) + self = .submitted(try SessionInputAnswered(from: decoder)) case "skipped": - self = .skipped(try ChatInputSkipped(from: decoder)) + self = .skipped(try SessionInputSkipped(from: decoder)) default: - throw DecodingError.dataCorruptedError(forKey: .discriminant, in: container, debugDescription: "Unknown ChatInputAnswer discriminant: \(discriminant)") + throw DecodingError.dataCorruptedError(forKey: .discriminant, in: container, debugDescription: "Unknown SessionInputAnswer discriminant: \(discriminant)") } } @@ -4612,11 +4417,10 @@ public enum ToolResultContent: Codable, Sendable { } } -/// The state payload of a snapshot — root, session, chat, terminal, changeset, resource-watch, or annotations state. +/// The state payload of a snapshot — root, session, terminal, changeset, resource-watch, or annotations state. public enum SnapshotState: Codable, Sendable { case root(RootState) case session(SessionState) - case chat(ChatState) case terminal(TerminalState) case changeset(ChangesetState) case resourceWatch(ResourceWatchState) @@ -4643,7 +4447,6 @@ public enum SnapshotState: Codable, Sendable { switch self { case .root(let state): try state.encode(to: encoder) case .session(let state): try state.encode(to: encoder) - case .chat(let state): try state.encode(to: encoder) case .terminal(let state): try state.encode(to: encoder) case .changeset(let state): try state.encode(to: encoder) case .resourceWatch(let state): try state.encode(to: encoder) diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/NativeReducer.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/NativeReducer.swift index 4b48352f..2afac4ce 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/NativeReducer.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/NativeReducer.swift @@ -109,24 +109,16 @@ public struct AHPRootReducer: Reducer { } } - -// MARK: - Chat Reducer (Protocol-based) - -/// Protocol-based chat reducer for AHP chat state. -public struct AHPChatReducer: Reducer { - public typealias State = ChatState - public typealias Action = StateAction - - public init() {} - - public func reduce(into state: inout ChatState, action: StateAction) { - state = chatReducer(state: state, action: action) - } -} - // MARK: - Session Reducer (Protocol-based) /// Protocol-based session reducer for AHP session state. +/// +/// This is a native Swift implementation of the session reducer using +/// idiomatic patterns: +/// - `inout` mutation instead of copy-on-write spread +/// - Guard-based early returns +/// - Pattern matching on enums +/// - Helper methods as private functions public struct AHPSessionReducer: Reducer { public typealias State = SessionState public typealias Action = StateAction @@ -134,7 +126,679 @@ public struct AHPSessionReducer: Reducer { public init() {} public func reduce(into state: inout SessionState, action: StateAction) { - state = sessionReducer(state: state, action: action) + switch action { + + // ── Lifecycle ────────────────────────────────────────────────────────── + + case .sessionReady: + state.lifecycle = .ready + state.summary.status = .idle + + case .sessionCreationFailed(let a): + state.lifecycle = .creationFailed + state.creationError = a.error + + // ── Turn Lifecycle ──────────────────────────────────────────────────── + + case .sessionTurnStarted(let a): + state.summary.modifiedAt = currentTimestamp() + state.activeTurn = ActiveTurn( + id: a.turnId, + message: a.message, + responseParts: [], + usage: nil + ) + // If auto-started from a pending message, remove it + if let queuedId = a.queuedMessageId { + if state.steeringMessage?.id == queuedId { + state.steeringMessage = nil + } + if var queued = state.queuedMessages { + queued.removeAll { $0.id == queuedId } + state.queuedMessages = queued.isEmpty ? nil : queued + } + } + state.summary.status = Self.withStatusFlag(Self.sessionSummaryStatus(state), .isRead, false) + + case .sessionDelta(let a): + Self.updateResponsePartInPlace(state: &state, turnId: a.turnId, partId: a.partId) { part in + guard case .markdown(var md) = part else { return } + md.content += a.content + part = .markdown(md) + } + + case .sessionResponsePart(let a): + guard state.activeTurn?.id == a.turnId else { return } + state.activeTurn?.responseParts.append(a.part) + + case .sessionTurnComplete(let a): + Self.endTurn(state: &state, turnId: a.turnId, turnState: .complete) + + case .sessionTurnCancelled(let a): + Self.endTurn(state: &state, turnId: a.turnId, turnState: .cancelled) + + case .sessionError(let a): + Self.endTurn(state: &state, turnId: a.turnId, turnState: .error, terminalStatus: .error, error: a.error) + + // ── Tool Call State Machine ─────────────────────────────────────────── + + case .sessionToolCallStart(let a): + guard state.activeTurn?.id == a.turnId else { return } + let toolCallPart = ToolCallResponsePart( + kind: .toolCall, + toolCall: .streaming(ToolCallStreamingState( + toolCallId: a.toolCallId, + toolName: a.toolName, + displayName: a.displayName, + contributor: a.contributor, + meta: a.meta, + status: .streaming + )) + ) + state.activeTurn?.responseParts.append(.toolCall(toolCallPart)) + + case .sessionToolCallDelta(let a): + Self.updateToolCallInPlace(state: &state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + guard case .streaming(var s) = tc else { return } + s.meta = a.meta ?? s.meta + s.partialInput = (s.partialInput ?? "") + a.content + if let msg = a.invocationMessage { + s.invocationMessage = msg + } + tc = .streaming(s) + } + + case .sessionToolCallReady(let a): + Self.updateToolCallInPlace(state: &state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + // Only process if currently streaming or running + switch tc { + case .streaming, .running: break + default: return + } + let base = tc.baseFields + let meta = a.meta ?? base.meta + if let confirmed = a.confirmed { + tc = .running(ToolCallRunningState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: meta, + invocationMessage: a.invocationMessage, + toolInput: a.toolInput, + status: .running, + confirmed: confirmed + )) + } else { + tc = .pendingConfirmation(ToolCallPendingConfirmationState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: meta, + invocationMessage: a.invocationMessage, + toolInput: a.toolInput, + status: .pendingConfirmation, + confirmationTitle: a.confirmationTitle, + edits: a.edits, + editable: a.editable, + options: a.options + )) + } + } + Self.refreshSummaryStatus(&state) + + case .sessionToolCallConfirmed(let a): + Self.updateToolCallInPlace(state: &state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + guard case .pendingConfirmation(let pending) = tc else { return } + let base = tc.baseFields + let meta = a.meta ?? base.meta + let selectedOption = Self.resolveSelectedOption(pending.options, id: a.selectedOptionId) + if a.approved { + tc = .running(ToolCallRunningState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: meta, + invocationMessage: pending.invocationMessage, + toolInput: a.editedToolInput ?? pending.toolInput, + status: .running, + confirmed: a.confirmed ?? .notNeeded, + selectedOption: selectedOption + )) + } else { + tc = .cancelled(ToolCallCancelledState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: meta, + invocationMessage: pending.invocationMessage, + toolInput: pending.toolInput, + status: .cancelled, + reason: a.reason ?? .denied, + reasonMessage: a.reasonMessage, + userSuggestion: a.userSuggestion, + selectedOption: selectedOption + )) + } + } + Self.refreshSummaryStatus(&state) + + case .sessionToolCallComplete(let a): + Self.updateToolCallInPlace(state: &state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + let base = tc.baseFields + let meta = a.meta ?? base.meta + let confirmed: ToolCallConfirmationReason + let invocationMessage: StringOrMarkdown + let toolInput: String? + let selectedOption: ConfirmationOption? + switch tc { + case .running(let r): + confirmed = r.confirmed + invocationMessage = r.invocationMessage + toolInput = r.toolInput + selectedOption = r.selectedOption + case .pendingConfirmation(let p): + confirmed = .notNeeded + invocationMessage = p.invocationMessage + toolInput = p.toolInput + selectedOption = nil + default: + return + } + + if a.requiresResultConfirmation == true { + tc = .pendingResultConfirmation(ToolCallPendingResultConfirmationState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: meta, + invocationMessage: invocationMessage, + toolInput: toolInput, + success: a.result.success, + pastTenseMessage: a.result.pastTenseMessage, + content: a.result.content, + structuredContent: a.result.structuredContent, + error: a.result.error, + status: .pendingResultConfirmation, + confirmed: confirmed, + selectedOption: selectedOption + )) + } else { + tc = .completed(ToolCallCompletedState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: meta, + invocationMessage: invocationMessage, + toolInput: toolInput, + success: a.result.success, + pastTenseMessage: a.result.pastTenseMessage, + content: a.result.content, + structuredContent: a.result.structuredContent, + error: a.result.error, + status: .completed, + confirmed: confirmed, + selectedOption: selectedOption + )) + } + } + Self.refreshSummaryStatus(&state) + + case .sessionToolCallResultConfirmed(let a): + Self.updateToolCallInPlace(state: &state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + guard case .pendingResultConfirmation(let prc) = tc else { return } + let base = tc.baseFields + let meta = a.meta ?? base.meta + if a.approved { + tc = .completed(ToolCallCompletedState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: meta, + invocationMessage: prc.invocationMessage, + toolInput: prc.toolInput, + success: prc.success, + pastTenseMessage: prc.pastTenseMessage, + content: prc.content, + structuredContent: prc.structuredContent, + error: prc.error, + status: .completed, + confirmed: prc.confirmed, + selectedOption: prc.selectedOption + )) + } else { + tc = .cancelled(ToolCallCancelledState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: meta, + invocationMessage: prc.invocationMessage, + toolInput: prc.toolInput, + status: .cancelled, + reason: .resultDenied, + selectedOption: prc.selectedOption + )) + } + } + Self.refreshSummaryStatus(&state) + + // ── Metadata ────────────────────────────────────────────────────────── + + case .sessionTitleChanged(let a): + state.summary.title = a.title + state.summary.modifiedAt = currentTimestamp() + + case .sessionUsage(let a): + guard state.activeTurn?.id == a.turnId else { return } + state.activeTurn?.usage = a.usage + + case .sessionReasoning(let a): + Self.updateResponsePartInPlace(state: &state, turnId: a.turnId, partId: a.partId) { part in + guard case .reasoning(var r) = part else { return } + r.content += a.content + part = .reasoning(r) + } + + case .sessionModelChanged(let a): + state.summary.model = a.model + state.summary.modifiedAt = currentTimestamp() + + case .sessionAgentChanged(let a): + state.summary.agent = a.agent + state.summary.modifiedAt = currentTimestamp() + + case .sessionActivityChanged(let a): + state.summary.activity = a.activity + + case .sessionConfigChanged(let a): + guard var config = state.config else { return } + config.values = a.replace == true ? a.config : config.values.merging(a.config) { _, new in new } + state.config = config + state.summary.modifiedAt = currentTimestamp() + + case .sessionMetaChanged(let a): + state.meta = a.meta + + case .sessionServerToolsChanged(let a): + state.serverTools = a.tools + + case .sessionActiveClientChanged(let a): + state.activeClient = a.activeClient + + case .sessionActiveClientToolsChanged(let a): + guard state.activeClient != nil else { return } + state.activeClient?.tools = a.tools + + // ── Customizations ────────────────────────────────────────────────── + + case .sessionCustomizationsChanged(let a): + state.customizations = a.customizations + + case .sessionCustomizationToggled(let a): + guard var list = state.customizations else { return } + if toggleCustomization(in: &list, id: a.id, enabled: a.enabled) { + state.customizations = list + } + + case .sessionCustomizationUpdated(let a): + var list = state.customizations ?? [] + if let idx = list.firstIndex(where: { customizationId($0) == customizationId(a.customization) }) { + list[idx] = a.customization + } else { + list.append(a.customization) + } + state.customizations = list + + case .sessionCustomizationRemoved(let a): + guard var list = state.customizations else { return } + if let idx = list.firstIndex(where: { customizationId($0) == a.id }) { + list.remove(at: idx) + state.customizations = list + return + } + for containerIdx in list.indices { + var container = list[containerIdx] + guard var children = customizationChildren(container) else { continue } + if let idx = children.firstIndex(where: { childId($0) == a.id }) { + children.remove(at: idx) + setCustomizationChildren(&container, children) + list[containerIdx] = container + state.customizations = list + return + } + } + + case .sessionMcpServerStatusChanged(let a): + guard var list = state.customizations else { return } + if let topIdx = list.firstIndex(where: { customizationId($0) == a.id }) { + guard case .mcpServer(var entry) = list[topIdx] else { return } + entry.state = a.state + entry.channel = a.channel + list[topIdx] = .mcpServer(entry) + state.customizations = list + return + } + for containerIdx in list.indices { + var container = list[containerIdx] + guard var children = customizationChildren(container) else { continue } + guard let childIdx = children.firstIndex(where: { childId($0) == a.id }) else { continue } + guard case .mcpServer(var child) = children[childIdx] else { continue } + child.state = a.state + child.channel = a.channel + children[childIdx] = .mcpServer(child) + setCustomizationChildren(&container, children) + list[containerIdx] = container + state.customizations = list + return + } + + // ── Truncation ──────────────────────────────────────────────────────── + + case .sessionTruncated(let a): + let turns: [Turn] + if let turnId = a.turnId { + guard let idx = state.turns.firstIndex(where: { $0.id == turnId }) else { + return + } + turns = Array(state.turns.prefix(idx + 1)) + } else { + turns = [] + } + state.turns = turns + state.activeTurn = nil + state.inputRequests = nil + state.summary.status = Self.sessionSummaryStatus(state) + state.summary.modifiedAt = currentTimestamp() + + // ── Read / Archived ───────────────────────────────────────────────── + + case .sessionIsReadChanged(let a): + state.summary.status = Self.withStatusFlag(state.summary.status, .isRead, a.isRead) + + case .sessionIsArchivedChanged(let a): + state.summary.status = Self.withStatusFlag(state.summary.status, .isArchived, a.isArchived) + + + // ── Tool Call Content ──────────────────────────────────────────────── + + case .sessionToolCallContentChanged(let a): + Self.updateToolCallInPlace(state: &state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + guard case .running(var r) = tc else { return } + r.meta = a.meta ?? r.meta + r.content = a.content + tc = .running(r) + } + + // ── Pending Messages ────────────────────────────────────────────────── + + case .sessionPendingMessageSet(let a): + let entry = PendingMessage(id: a.id, message: a.message) + if a.kind == .steering { + state.steeringMessage = entry + return + } + if let idx = state.queuedMessages?.firstIndex(where: { $0.id == a.id }) { + state.queuedMessages?[idx] = entry + } else { + var existing = state.queuedMessages ?? [] + existing.append(entry) + state.queuedMessages = existing + } + + case .sessionPendingMessageRemoved(let a): + if a.kind == .steering { + guard state.steeringMessage?.id == a.id else { return } + state.steeringMessage = nil + return + } + guard var existing = state.queuedMessages else { return } + let before = existing.count + existing.removeAll { $0.id == a.id } + guard existing.count != before else { return } + state.queuedMessages = existing.isEmpty ? nil : existing + + case .sessionQueuedMessagesReordered(let a): + guard let existing = state.queuedMessages else { return } + let byId = Dictionary(uniqueKeysWithValues: existing.map { ($0.id, $0) }) + var ordered = Set() + var reordered: [PendingMessage] = a.order.compactMap { id in + guard let msg = byId[id], !ordered.contains(id) else { return nil } + ordered.insert(id) + return msg + } + for m in existing where !ordered.contains(m.id) { + reordered.append(m) + } + state.queuedMessages = reordered + + // ── Session Input Requests ───────────────────────────────────────────── + + case .sessionInputRequested(let a): + Self.upsertInputRequest(state: &state, request: a.request) + + case .sessionInputAnswerChanged(let a): + guard var existing = state.inputRequests, + let idx = existing.firstIndex(where: { $0.id == a.requestId }) else { + return + } + var request = existing[idx] + var answers = request.answers ?? [:] + if let answer = a.answer { + answers[a.questionId] = answer + } else { + answers.removeValue(forKey: a.questionId) + } + request.answers = answers.isEmpty ? nil : answers + existing[idx] = request + state.inputRequests = existing + state.summary.modifiedAt = currentTimestamp() + + case .sessionInputCompleted(let a): + guard var existing = state.inputRequests, + existing.contains(where: { $0.id == a.requestId }) else { + return + } + existing.removeAll { $0.id == a.requestId } + state.inputRequests = existing.isEmpty ? nil : existing + state.summary.status = Self.sessionSummaryStatus(state) + state.summary.modifiedAt = currentTimestamp() + + default: + break + } + } + + // MARK: - Private Helpers + + /// Bitmask covering the mutually-exclusive activity bits (bits 0–4). + private static let statusActivityMask = SessionStatus(rawValue: (1 << 5) - 1) + + /// Sets or clears a metadata flag on a status value. + private static func withStatusFlag(_ status: SessionStatus, _ flag: SessionStatus, _ set: Bool) -> SessionStatus { + set ? status.union(flag) : status.subtracting(flag) + } + + // ToolCallBaseFields and toolCallBase() are now shared via + // ToolCallState.baseFields in ToolCallStateExtensions.swift. + + /// Ends the active turn, producing a completed Turn record. + /// Non-terminal tool calls are forced to cancelled. + private static func endTurn( + state: inout SessionState, + turnId: String, + turnState: TurnState, + terminalStatus: SessionStatus? = nil, + error: ErrorInfo? = nil + ) { + guard let activeTurn = state.activeTurn, activeTurn.id == turnId else { return } + + let responseParts: [ResponsePart] = activeTurn.responseParts.map { part in + guard case .toolCall(let tcPart) = part else { return part } + let tc = tcPart.toolCall + switch tc { + case .completed, .cancelled: + return part + default: + let base = tc.baseFields + let invocationMessage: StringOrMarkdown + let toolInput: String? + switch tc { + case .streaming(let s): + invocationMessage = s.invocationMessage ?? .string("") + toolInput = nil + case .pendingConfirmation(let p): + invocationMessage = p.invocationMessage + toolInput = p.toolInput + case .running(let r): + invocationMessage = r.invocationMessage + toolInput = r.toolInput + case .pendingResultConfirmation(let r): + invocationMessage = r.invocationMessage + toolInput = r.toolInput + default: + invocationMessage = .string("") + toolInput = nil + } + return .toolCall(ToolCallResponsePart( + kind: .toolCall, + toolCall: .cancelled(ToolCallCancelledState( + toolCallId: base.toolCallId, + toolName: base.toolName, + displayName: base.displayName, + contributor: base.contributor, + meta: base.meta, + invocationMessage: invocationMessage, + toolInput: toolInput, + status: .cancelled, + reason: .skipped + )) + )) + } + } + + let turn = Turn( + id: activeTurn.id, + message: activeTurn.message, + responseParts: responseParts, + usage: activeTurn.usage, + state: turnState, + error: error + ) + + state.turns.append(turn) + state.activeTurn = nil + state.inputRequests = nil + state.summary.status = Self.sessionSummaryStatus(state, terminalStatus: terminalStatus) + state.summary.modifiedAt = currentTimestamp() + } + + private static func sessionSummaryStatus(_ state: SessionState, terminalStatus: SessionStatus? = nil) -> SessionStatus { + let activity: SessionStatus + if let terminalStatus { + activity = terminalStatus + } else if state.inputRequests?.isEmpty == false || Self.hasPendingToolCallConfirmation(state) { + activity = .inputNeeded + } else if state.activeTurn != nil { + activity = .inProgress + } else { + activity = .idle + } + return state.summary.status.subtracting(Self.statusActivityMask).union(activity) + } + + /// Returns `true` if the active turn has any tool call awaiting user confirmation. + private static func hasPendingToolCallConfirmation(_ state: SessionState) -> Bool { + guard let activeTurn = state.activeTurn else { return false } + for part in activeTurn.responseParts { + guard case .toolCall(let tcPart) = part else { continue } + switch tcPart.toolCall { + case .pendingConfirmation, .pendingResultConfirmation: + return true + default: + continue + } + } + return false + } + + private static func resolveSelectedOption(_ options: [ConfirmationOption]?, id: String?) -> ConfirmationOption? { + guard let id, let options else { + return nil + } + return options.first { $0.id == id } + } + + /// Recomputes `summary.status` from the current state in place. + private static func refreshSummaryStatus(_ state: inout SessionState) { + state.summary.status = Self.sessionSummaryStatus(state) + } + + private static func upsertInputRequest(state: inout SessionState, request: SessionInputRequest) { + var existing = state.inputRequests ?? [] + if let idx = existing.firstIndex(where: { $0.id == request.id }) { + var replacement = request + replacement.answers = request.answers ?? existing[idx].answers + existing[idx] = replacement + } else { + existing.append(request) + } + state.inputRequests = existing + state.summary.status = Self.withStatusFlag(Self.sessionSummaryStatus(state), .isRead, false) + state.summary.modifiedAt = currentTimestamp() + } + + /// Updates a tool call inside the active turn's response parts in place. + private static func updateToolCallInPlace( + state: inout SessionState, + turnId: String, + toolCallId: String, + updater: (inout ToolCallState) -> Void + ) { + guard state.activeTurn?.id == turnId else { return } + guard let parts = state.activeTurn?.responseParts else { return } + + var found = false + let newParts: [ResponsePart] = parts.map { part in + guard case .toolCall(var tcPart) = part else { return part } + guard tcPart.toolCall.toolCallId == toolCallId else { return part } + found = true + updater(&tcPart.toolCall) + return .toolCall(tcPart) + } + + guard found else { return } + state.activeTurn?.responseParts = newParts + } + + /// Updates a response part identified by partId in the active turn in place. + private static func updateResponsePartInPlace( + state: inout SessionState, + turnId: String, + partId: String, + updater: (inout ResponsePart) -> Void + ) { + guard state.activeTurn?.id == turnId else { return } + guard let parts = state.activeTurn?.responseParts else { return } + + var found = false + let newParts: [ResponsePart] = parts.map { part in + guard !found else { return part } + guard part.partId == partId else { return part } + found = true + var mutable = part + updater(&mutable) + return mutable + } + + guard found else { return } + state.activeTurn?.responseParts = newParts } } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift index ff616048..0b91b0ca 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Reducers.swift @@ -10,13 +10,6 @@ public var currentTimestampProvider: () -> Int = { Int(Date().timeIntervalSince1970 * 1000) } -private let iso8601TimestampFormatter: ISO8601DateFormatter = { - let formatter = ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - formatter.timeZone = TimeZone(secondsFromGMT: 0) - return formatter -}() - // MARK: - Status Bitset Helpers /// Bitmask covering the mutually-exclusive activity bits (bits 0–4). @@ -69,24 +62,39 @@ public func rootReducer(state: RootState, action: StateAction) -> RootState { } } +// MARK: - Session Reducer -// MARK: - Chat Reducer - -/// Pure reducer for chat state. -public func chatReducer(state: ChatState, action: StateAction) -> ChatState { +/// Pure reducer for session state. +public func sessionReducer(state: SessionState, action: StateAction) -> SessionState { switch action { + // ── Lifecycle ────────────────────────────────────────────────────────── + + case .sessionReady: + // Lifecycle-only transition. Must not touch `summary.status`: see + // the equivalent TypeScript reducer for the rationale. + var next = state + next.lifecycle = .ready + return next + + case .sessionCreationFailed(let a): + var next = state + next.lifecycle = .creationFailed + next.creationError = a.error + return next + // ── Turn Lifecycle ──────────────────────────────────────────────────── - case .chatTurnStarted(let a): + case .sessionTurnStarted(let a): var next = state - next.modifiedAt = currentTimestamp() + next.summary.modifiedAt = currentTimestamp() next.activeTurn = ActiveTurn( id: a.turnId, message: a.message, responseParts: [], usage: nil ) + // If auto-started from a pending message, remove it if let queuedId = a.queuedMessageId { if next.steeringMessage?.id == queuedId { next.steeringMessage = nil @@ -96,17 +104,17 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { next.queuedMessages = queued.isEmpty ? nil : queued } } - next.status = withStatusFlag(chatSummaryStatus(next), .isRead, false) + next.summary.status = withStatusFlag(sessionSummaryStatus(next), .isRead, false) return next - case .chatDelta(let a): + case .sessionDelta(let a): return updateResponsePart(state: state, turnId: a.turnId, partId: a.partId) { part in guard case .markdown(var md) = part else { return part } md.content += a.content return .markdown(md) } - case .chatResponsePart(let a): + case .sessionResponsePart(let a): guard var activeTurn = state.activeTurn, activeTurn.id == a.turnId else { return state } @@ -115,18 +123,18 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { next.activeTurn = activeTurn return next - case .chatTurnComplete(let a): + case .sessionTurnComplete(let a): return endTurn(state: state, turnId: a.turnId, turnState: .complete) - case .chatTurnCancelled(let a): + case .sessionTurnCancelled(let a): return endTurn(state: state, turnId: a.turnId, turnState: .cancelled) - case .chatError(let a): + case .sessionError(let a): return endTurn(state: state, turnId: a.turnId, turnState: .error, terminalStatus: .error, error: a.error) // ── Tool Call State Machine ─────────────────────────────────────────── - case .chatToolCallStart(let a): + case .sessionToolCallStart(let a): guard var activeTurn = state.activeTurn, activeTurn.id == a.turnId else { return state } @@ -146,19 +154,20 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { next.activeTurn = activeTurn return next - case .chatToolCallDelta(let a): + case .sessionToolCallDelta(let a): return updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in guard case .streaming(var s) = tc else { return tc } + s.meta = a.meta ?? s.meta s.partialInput = (s.partialInput ?? "") + a.content if let msg = a.invocationMessage { s.invocationMessage = msg } - s.meta = a.meta ?? s.meta return .streaming(s) } - case .chatToolCallReady(let a): - return refreshChatSummaryStatus(updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + case .sessionToolCallReady(let a): + return refreshSummaryStatus(updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + // Only process if currently streaming or running (matches TS behavior) switch tc { case .streaming, .running: break default: return tc @@ -194,8 +203,8 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { )) }) - case .chatToolCallConfirmed(let a): - return refreshChatSummaryStatus(updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + case .sessionToolCallConfirmed(let a): + return refreshSummaryStatus(updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in guard case .pendingConfirmation(let pending) = tc else { return tc } let base = tc.baseFields let meta = a.meta ?? base.meta @@ -230,8 +239,8 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { )) }) - case .chatToolCallComplete(let a): - return refreshChatSummaryStatus(updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + case .sessionToolCallComplete(let a): + return refreshSummaryStatus(updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in let base = tc.baseFields let meta = a.meta ?? base.meta let confirmed: ToolCallConfirmationReason @@ -291,8 +300,8 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { )) }) - case .chatToolCallResultConfirmed(let a): - return refreshChatSummaryStatus(updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + case .sessionToolCallResultConfirmed(let a): + return refreshSummaryStatus(updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in guard case .pendingResultConfirmation(let prc) = tc else { return tc } let base = tc.baseFields let meta = a.meta ?? base.meta @@ -329,15 +338,15 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { )) }) - case .chatToolCallContentChanged(let a): - return updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in - guard case .running(var r) = tc else { return tc } - r.meta = a.meta ?? r.meta - r.content = a.content - return .running(r) - } + // ── Metadata ────────────────────────────────────────────────────────── - case .chatUsage(let a): + case .sessionTitleChanged(let a): + var next = state + next.summary.title = a.title + next.summary.modifiedAt = currentTimestamp() + return next + + case .sessionUsage(let a): guard var activeTurn = state.activeTurn, activeTurn.id == a.turnId else { return state } @@ -346,202 +355,23 @@ public func chatReducer(state: ChatState, action: StateAction) -> ChatState { next.activeTurn = activeTurn return next - case .chatReasoning(let a): + case .sessionReasoning(let a): return updateResponsePart(state: state, turnId: a.turnId, partId: a.partId) { part in guard case .reasoning(var r) = part else { return part } r.content += a.content return .reasoning(r) } - // ── Truncation ──────────────────────────────────────────────────────── - - case .chatTruncated(let a): - let turns: [Turn] - if let turnId = a.turnId { - guard let idx = state.turns.firstIndex(where: { $0.id == turnId }) else { - return state - } - turns = Array(state.turns.prefix(idx + 1)) - } else { - turns = [] - } - var next = state - next.turns = turns - next.activeTurn = nil - next.inputRequests = nil - next.status = chatSummaryStatus(next) - next.modifiedAt = currentTimestamp() - return next - - // ── Session Input Requests ───────────────────────────────────────────── - - case .chatInputRequested(let a): - return upsertInputRequest(state: state, request: a.request) - - case .chatInputAnswerChanged(let a): - guard var existing = state.inputRequests, - let idx = existing.firstIndex(where: { $0.id == a.requestId }) else { - return state - } - var request = existing[idx] - var answers = request.answers ?? [:] - if let answer = a.answer { - answers[a.questionId] = answer - } else { - answers.removeValue(forKey: a.questionId) - } - request.answers = answers.isEmpty ? nil : answers - existing[idx] = request - var next = state - next.inputRequests = existing - next.modifiedAt = currentTimestamp() - return next - - case .chatInputCompleted(let a): - guard var existing = state.inputRequests, - existing.contains(where: { $0.id == a.requestId }) else { - return state - } - existing.removeAll { $0.id == a.requestId } - var next = state - next.inputRequests = existing.isEmpty ? nil : existing - next.status = chatSummaryStatus(next) - next.modifiedAt = currentTimestamp() - return next - - // ── Pending Messages ────────────────────────────────────────────────── - - case .chatPendingMessageSet(let a): - let entry = PendingMessage(id: a.id, message: a.message) - var next = state - if a.kind == .steering { - next.steeringMessage = entry - return next - } - 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 { - guard next.steeringMessage?.id == a.id else { return state } - next.steeringMessage = nil - return next - } - 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 } - let byId = Dictionary(uniqueKeysWithValues: existing.map { ($0.id, $0) }) - var ordered = Set() - var reordered: [PendingMessage] = a.order.compactMap { id in - guard let msg = byId[id], !ordered.contains(id) else { return nil } - ordered.insert(id) - return msg - } - for m in existing where !ordered.contains(m.id) { - reordered.append(m) - } - var next = state - next.queuedMessages = reordered - return next - - default: - return state - } -} - -// MARK: - Session Reducer - -/// Pure reducer for session state. -public func sessionReducer(state: SessionState, action: StateAction) -> SessionState { - switch action { - - // ── Lifecycle ────────────────────────────────────────────────────────── - - case .sessionReady: - var next = state - next.lifecycle = .ready - return next - - case .sessionCreationFailed(let a): - var next = state - next.lifecycle = .creationFailed - next.creationError = a.error - return next - - case .sessionChatAdded(let a): - var next = state - if let idx = next.chats.firstIndex(where: { $0.resource == a.summary.resource }) { - next.chats[idx] = a.summary - } else { - next.chats.append(a.summary) - } - return next - - case .sessionChatRemoved(let a): - guard let idx = state.chats.firstIndex(where: { $0.resource == a.chat }) else { - return state - } - var next = state - next.chats.remove(at: idx) - if next.defaultChat == a.chat { - next.defaultChat = nil - } - return next - - case .sessionChatUpdated(let a): - guard let idx = state.chats.firstIndex(where: { $0.resource == a.chat }) else { - return state - } - var next = state - mergeChatSummaryChanges(&next.chats[idx], changes: a.changes) - return next - - case .sessionDefaultChatChanged(let a): - var next = state - next.defaultChat = a.defaultChat - return next - - // ── Metadata ────────────────────────────────────────────────────────── - - case .sessionTitleChanged(let a): - var next = state - next.summary.title = a.title - next.summary.modifiedAt = currentTimestampMillis() - return next - case .sessionModelChanged(let a): var next = state next.summary.model = a.model - next.summary.modifiedAt = currentTimestampMillis() + next.summary.modifiedAt = currentTimestamp() return next case .sessionAgentChanged(let a): var next = state next.summary.agent = a.agent - next.summary.modifiedAt = currentTimestampMillis() - return next - - case .sessionIsReadChanged(let a): - var next = state - next.summary.status = withStatusFlag(next.summary.status, .isRead, a.isRead) - return next - - case .sessionIsArchivedChanged(let a): - var next = state - next.summary.status = withStatusFlag(next.summary.status, .isArchived, a.isArchived) + next.summary.modifiedAt = currentTimestamp() return next case .sessionActivityChanged(let a): @@ -559,7 +389,7 @@ public func sessionReducer(state: SessionState, action: StateAction) -> SessionS config.values = a.replace == true ? a.config : config.values.merging(a.config) { _, new in new } var next = state next.config = config - next.summary.modifiedAt = currentTimestampMillis() + next.summary.modifiedAt = currentTimestamp() return next case .sessionMetaChanged(let a): @@ -631,7 +461,7 @@ public func sessionReducer(state: SessionState, action: StateAction) -> SessionS } return state - case .sessionMcpServerStateChanged(let a): + case .sessionMcpServerStatusChanged(let a): guard var list = state.customizations else { return state } if let topIdx = list.firstIndex(where: { customizationId($0) == a.id }) { guard case .mcpServer(var entry) = list[topIdx] else { return state } @@ -642,6 +472,7 @@ public func sessionReducer(state: SessionState, action: StateAction) -> SessionS next.customizations = list return next } + var changed = false for containerIdx in list.indices { var container = list[containerIdx] guard var children = customizationChildren(container) else { continue } @@ -652,11 +483,141 @@ public func sessionReducer(state: SessionState, action: StateAction) -> SessionS children[childIdx] = .mcpServer(child) setCustomizationChildren(&container, children) list[containerIdx] = container - var next = state - next.customizations = list + changed = true + break + } + guard changed else { return state } + var next = state + next.customizations = list + return next + + // ── Truncation ──────────────────────────────────────────────────────── + + case .sessionTruncated(let a): + let turns: [Turn] + if let turnId = a.turnId { + guard let idx = state.turns.firstIndex(where: { $0.id == turnId }) else { + return state + } + turns = Array(state.turns.prefix(idx + 1)) + } else { + turns = [] + } + var next = state + next.turns = turns + next.activeTurn = nil + next.inputRequests = nil + next.summary.status = sessionSummaryStatus(next) + next.summary.modifiedAt = currentTimestamp() + return next + + // ── Read / Archived ───────────────────────────────────────────────── + + case .sessionIsReadChanged(let a): + var next = state + next.summary.status = withStatusFlag(next.summary.status, .isRead, a.isRead) + return next + + case .sessionIsArchivedChanged(let a): + var next = state + next.summary.status = withStatusFlag(next.summary.status, .isArchived, a.isArchived) + return next + + + // ── Tool Call Content ──────────────────────────────────────────────── + + case .sessionToolCallContentChanged(let a): + return updateToolCall(state: state, turnId: a.turnId, toolCallId: a.toolCallId) { tc in + guard case .running(var r) = tc else { return tc } + r.meta = a.meta ?? r.meta + r.content = a.content + return .running(r) + } + + // ── Pending Messages ────────────────────────────────────────────────── + + case .sessionPendingMessageSet(let a): + let entry = PendingMessage(id: a.id, message: a.message) + var next = state + if a.kind == .steering { + next.steeringMessage = entry return next } - 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 .sessionPendingMessageRemoved(let a): + var next = state + if a.kind == .steering { + guard next.steeringMessage?.id == a.id else { return state } + next.steeringMessage = nil + return next + } + 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 .sessionQueuedMessagesReordered(let a): + guard let existing = state.queuedMessages else { return state } + let byId = Dictionary(uniqueKeysWithValues: existing.map { ($0.id, $0) }) + var ordered = Set() + var reordered: [PendingMessage] = a.order.compactMap { id in + guard let msg = byId[id], !ordered.contains(id) else { return nil } + ordered.insert(id) + return msg + } + // Append any messages not in the new order + for m in existing where !ordered.contains(m.id) { + reordered.append(m) + } + var next = state + next.queuedMessages = reordered + return next + + // ── Session Input Requests ───────────────────────────────────────────── + + case .sessionInputRequested(let a): + return upsertInputRequest(state: state, request: a.request) + + case .sessionInputAnswerChanged(let a): + guard var existing = state.inputRequests, + let idx = existing.firstIndex(where: { $0.id == a.requestId }) else { + return state + } + var request = existing[idx] + var answers = request.answers ?? [:] + if let answer = a.answer { + answers[a.questionId] = answer + } else { + answers.removeValue(forKey: a.questionId) + } + request.answers = answers.isEmpty ? nil : answers + existing[idx] = request + var next = state + next.inputRequests = existing + next.summary.modifiedAt = currentTimestamp() + return next + + case .sessionInputCompleted(let a): + guard var existing = state.inputRequests, + existing.contains(where: { $0.id == a.requestId }) else { + return state + } + existing.removeAll { $0.id == a.requestId } + var next = state + next.inputRequests = existing.isEmpty ? nil : existing + next.summary.status = sessionSummaryStatus(next) + next.summary.modifiedAt = currentTimestamp() + return next default: return state @@ -667,20 +628,19 @@ public func sessionReducer(state: SessionState, action: StateAction) -> SessionS /// Set of action types that clients are allowed to dispatch. public let clientDispatchableActions: Set = [ - "chat/turnStarted", - "chat/toolCallConfirmed", - "chat/toolCallComplete", - "chat/toolCallResultConfirmed", - "chat/turnCancelled", + "session/turnStarted", + "session/toolCallConfirmed", + "session/toolCallComplete", + "session/toolCallResultConfirmed", + "session/turnCancelled", "session/modelChanged", - "session/agentChanged", "session/activeClientChanged", "session/activeClientToolsChanged", - "chat/pendingMessageSet", - "chat/pendingMessageRemoved", - "chat/queuedMessagesReordered", - "chat/inputAnswerChanged", - "chat/inputCompleted", + "session/pendingMessageSet", + "session/pendingMessageRemoved", + "session/queuedMessagesReordered", + "session/inputAnswerChanged", + "session/inputCompleted", "session/customizationToggled", "session/isReadChanged", "session/isArchivedChanged", @@ -689,12 +649,12 @@ public let clientDispatchableActions: Set = [ /// Checks whether an action may be dispatched by a client. public func isClientDispatchable(_ action: StateAction) -> Bool { switch action { - case .chatTurnStarted, .chatToolCallConfirmed, .chatToolCallComplete, - .chatToolCallResultConfirmed, .chatTurnCancelled, - .sessionModelChanged, .sessionAgentChanged, .sessionActiveClientChanged, - .sessionActiveClientToolsChanged, .chatPendingMessageSet, - .chatPendingMessageRemoved, .chatQueuedMessagesReordered, - .chatInputAnswerChanged, .chatInputCompleted, + case .sessionTurnStarted, .sessionToolCallConfirmed, .sessionToolCallComplete, + .sessionToolCallResultConfirmed, .sessionTurnCancelled, + .sessionModelChanged, .sessionActiveClientChanged, + .sessionActiveClientToolsChanged, .sessionPendingMessageSet, + .sessionPendingMessageRemoved, .sessionQueuedMessagesReordered, + .sessionInputAnswerChanged, .sessionInputCompleted, .sessionCustomizationToggled, .sessionIsReadChanged, .sessionIsArchivedChanged: return true @@ -705,27 +665,11 @@ public func isClientDispatchable(_ action: StateAction) -> Bool { // MARK: - Helpers -private func currentTimestampMillis() -> Int { +private func currentTimestamp() -> Int { currentTimestampProvider() } -private func currentTimestamp() -> String { - let date = Date(timeIntervalSince1970: Double(currentTimestampProvider()) / 1000) - return iso8601TimestampFormatter.string(from: date) -} - -private func mergeChatSummaryChanges(_ summary: inout ChatSummary, changes: PartialChatSummary) { - if let title = changes.title { summary.title = title } - if let status = changes.status { summary.status = status } - if let activity = changes.activity { summary.activity = activity } - if let modifiedAt = changes.modifiedAt { summary.modifiedAt = modifiedAt } - if let model = changes.model { summary.model = model } - if let agent = changes.agent { summary.agent = agent } - if let origin = changes.origin { summary.origin = origin } - if let workingDirectory = changes.workingDirectory { summary.workingDirectory = workingDirectory } -} - -private func chatSummaryStatus(_ state: ChatState, terminalStatus: SessionStatus? = nil) -> SessionStatus { +private func sessionSummaryStatus(_ state: SessionState, terminalStatus: SessionStatus? = nil) -> SessionStatus { let activity: SessionStatus if let terminalStatus { activity = terminalStatus @@ -736,11 +680,11 @@ private func chatSummaryStatus(_ state: ChatState, terminalStatus: SessionStatus } else { activity = .idle } - return state.status.subtracting(statusActivityMask).union(activity) + return state.summary.status.subtracting(statusActivityMask).union(activity) } /// Returns `true` if the active turn has any tool call awaiting user confirmation. -private func hasPendingToolCallConfirmation(_ state: ChatState) -> Bool { +private func hasPendingToolCallConfirmation(_ state: SessionState) -> Bool { guard let activeTurn = state.activeTurn else { return false } for part in activeTurn.responseParts { guard case .toolCall(let tcPart) = part else { continue } @@ -754,15 +698,18 @@ private func hasPendingToolCallConfirmation(_ state: ChatState) -> Bool { return false } -private func refreshChatSummaryStatus(_ state: ChatState) -> ChatState { - let status = chatSummaryStatus(state) - guard status != state.status else { return state } +/// Returns a state with `summary.status` recomputed. Use this after reducers +/// that change data feeding into `sessionSummaryStatus` (e.g. tool call +/// lifecycle transitions that may enter or leave a pending-confirmation state). +private func refreshSummaryStatus(_ state: SessionState) -> SessionState { + let status = sessionSummaryStatus(state) + guard status != state.summary.status else { return state } var next = state - next.status = status + next.summary.status = status return next } -private func upsertInputRequest(state: ChatState, request: ChatInputRequest) -> ChatState { +private func upsertInputRequest(state: SessionState, request: SessionInputRequest) -> SessionState { var next = state var existing = next.inputRequests ?? [] if let idx = existing.firstIndex(where: { $0.id == request.id }) { @@ -773,20 +720,23 @@ private func upsertInputRequest(state: ChatState, request: ChatInputRequest) -> existing.append(request) } next.inputRequests = existing - next.status = withStatusFlag(chatSummaryStatus(next), .isRead, false) - next.modifiedAt = currentTimestamp() + next.summary.status = withStatusFlag(sessionSummaryStatus(next), .isRead, false) + next.summary.modifiedAt = currentTimestamp() return next } +// ToolCallBaseFields and toolCallBase() are now shared via +// ToolCallState.baseFields in ToolCallStateExtensions.swift. + /// Ends the active turn, producing a completed Turn record. /// Non-terminal tool calls are forced to cancelled. private func endTurn( - state: ChatState, + state: SessionState, turnId: String, turnState: TurnState, terminalStatus: SessionStatus? = nil, error: ErrorInfo? = nil -) -> ChatState { +) -> SessionState { guard let activeTurn = state.activeTurn, activeTurn.id == turnId else { return state } @@ -848,18 +798,18 @@ private func endTurn( next.turns.append(turn) next.activeTurn = nil next.inputRequests = nil - next.status = chatSummaryStatus(next, terminalStatus: terminalStatus) - next.modifiedAt = currentTimestamp() + next.summary.status = sessionSummaryStatus(next, terminalStatus: terminalStatus) + next.summary.modifiedAt = currentTimestamp() return next } /// Updates a tool call inside the active turn's response parts. private func updateToolCall( - state: ChatState, + state: SessionState, turnId: String, toolCallId: String, updater: (ToolCallState) -> ToolCallState -) -> ChatState { +) -> SessionState { guard var activeTurn = state.activeTurn, activeTurn.id == turnId else { return state } @@ -882,11 +832,11 @@ private func updateToolCall( /// Updates a response part identified by partId in the active turn. private func updateResponsePart( - state: ChatState, + state: SessionState, turnId: String, partId: String, updater: (ResponsePart) -> ResponsePart -) -> ChatState { +) -> SessionState { guard var activeTurn = state.activeTurn, activeTurn.id == turnId else { return state } diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift index 7c44fb33..e61e0a19 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/AHPStateMirror.swift @@ -13,7 +13,6 @@ import AgentHostProtocol public actor AHPStateMirror { public private(set) var rootState: RootState = RootState(agents: []) public private(set) var sessions: [String: SessionState] = [:] - public private(set) var chats: [String: ChatState] = [:] public private(set) var terminals: [String: TerminalState] = [:] public private(set) var changesets: [String: ChangesetState] = [:] public private(set) var annotations: [String: AnnotationsState] = [:] @@ -34,19 +33,16 @@ public actor AHPStateMirror { rootState = rootReducer(state: rootState, action: action) return } - if channel.hasPrefix("ahp-session:"), var session = sessions[channel] { + if var session = sessions[channel] { session = sessionReducer(state: session, action: action) sessions[channel] = session return } - if channel.hasPrefix("ahp-chat:"), var chat = chats[channel] { - chat = chatReducer(state: chat, action: action) - chats[channel] = chat - return - } - if channel.hasPrefix("ahp-terminal:"), var terminal = terminals[channel] { - terminal = terminalReducer(state: terminal, action: action) - terminals[channel] = terminal + if terminals[channel] != nil { + // Terminals don't have a hand-written reducer in the Swift + // package today; just leave the slot as the latest snapshot. + // (Native reducer + state shape will be wired up when + // terminal lifecycle reducers ship.) return } if changesets[channel] != nil { @@ -76,8 +72,6 @@ public actor AHPStateMirror { rootState = state case .session(let state): sessions[snapshot.resource] = state - case .chat(let state): - chats[snapshot.resource] = state case .terminal(let state): terminals[snapshot.resource] = state case .changeset(let state): @@ -93,7 +87,6 @@ public actor AHPStateMirror { public func reset() { rootState = RootState(agents: []) sessions.removeAll() - chats.removeAll() terminals.removeAll() changesets.removeAll() annotations.removeAll() diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift index 767b7ef2..2ab48ddd 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocolClient/MultiHostStateMirror.swift @@ -45,7 +45,6 @@ public struct HostedResourceKey: Hashable, Sendable { public actor MultiHostStateMirror { public private(set) var rootStates: [HostId: RootState] = [:] public private(set) var sessions: [HostedResourceKey: SessionState] = [:] - public private(set) var chats: [HostedResourceKey: ChatState] = [:] public private(set) var terminals: [HostedResourceKey: TerminalState] = [:] public private(set) var changesets: [HostedResourceKey: ChangesetState] = [:] public private(set) var annotations: [HostedResourceKey: AnnotationsState] = [:] @@ -75,19 +74,14 @@ public actor MultiHostStateMirror { return } let key = HostedResourceKey(hostId: host, uri: channel) - if channel.hasPrefix("ahp-session:"), var session = sessions[key] { + if var session = sessions[key] { session = sessionReducer(state: session, action: action) sessions[key] = session return } - if channel.hasPrefix("ahp-chat:"), var chat = chats[key] { - chat = chatReducer(state: chat, action: action) - chats[key] = chat - return - } - if channel.hasPrefix("ahp-terminal:"), var terminal = terminals[key] { - terminal = terminalReducer(state: terminal, action: action) - terminals[key] = terminal + if terminals[key] != nil { + // Terminals don't have a hand-written reducer in the Swift + // package today; just leave the slot as the latest snapshot. return } if changesets[key] != nil { @@ -120,8 +114,6 @@ public actor MultiHostStateMirror { rootStates[host] = state case .session(let state): sessions[key] = state - case .chat(let state): - chats[key] = state case .terminal(let state): terminals[key] = state case .changeset(let state): @@ -140,7 +132,6 @@ public actor MultiHostStateMirror { public func reset(host: HostId) { rootStates.removeValue(forKey: host) sessions = sessions.filter { $0.key.hostId != host } - chats = chats.filter { $0.key.hostId != host } terminals = terminals.filter { $0.key.hostId != host } changesets = changesets.filter { $0.key.hostId != host } annotations = annotations.filter { $0.key.hostId != host } @@ -151,7 +142,6 @@ public actor MultiHostStateMirror { public func reset() { rootStates.removeAll() sessions.removeAll() - chats.removeAll() terminals.removeAll() changesets.removeAll() annotations.removeAll() diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/AHPClientTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/AHPClientTests.swift index 35561a82..583d19e6 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/AHPClientTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/AHPClientTests.swift @@ -58,7 +58,7 @@ final class AHPClientTests: XCTestCase { createdAt: 1, modifiedAt: 1 ), lifecycle: .ready, - chats: [] + turns: [] )), fromSeq: 0 )) diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/AHPStateMirrorTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/AHPStateMirrorTests.swift index 9ba13d1a..f294d303 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/AHPStateMirrorTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/AHPStateMirrorTests.swift @@ -33,7 +33,7 @@ final class AHPStateMirrorTests: XCTestCase { createdAt: 1, modifiedAt: 1 ), lifecycle: .ready, - chats: [] + turns: [] ) let snapshot = Snapshot( resource: "ahp-session:/s1", @@ -75,7 +75,7 @@ final class AHPStateMirrorTests: XCTestCase { createdAt: 1, modifiedAt: 1 ), lifecycle: .ready, - chats: [] + turns: [] ) await mirror.applySnapshot(Snapshot( resource: "ahp-session:/s1", diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/MultiHostStateMirrorTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/MultiHostStateMirrorTests.swift index 1d9c8568..9de46c40 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/MultiHostStateMirrorTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolClientTests/MultiHostStateMirrorTests.swift @@ -39,14 +39,14 @@ final class MultiHostStateMirrorTests: XCTestCase { resource: "ahp-session:/s1", provider: "x", title: "A title", status: .idle, createdAt: 1, modifiedAt: 1 ), - lifecycle: .ready, chats: [] + lifecycle: .ready, turns: [] ) let sessionB = SessionState( summary: SessionSummary( resource: "ahp-session:/s1", provider: "x", title: "B title", status: .idle, createdAt: 1, modifiedAt: 1 ), - lifecycle: .ready, chats: [] + lifecycle: .ready, turns: [] ) await mirror.applySnapshot( @@ -101,7 +101,7 @@ final class MultiHostStateMirrorTests: XCTestCase { resource: "ahp-session:/s1", provider: "x", title: "Old", status: .idle, createdAt: 1, modifiedAt: 1 ), - lifecycle: .ready, chats: [] + lifecycle: .ready, turns: [] ) await mirror.applySnapshot( host: "alpha", diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift index 3d2dffd3..27665ba1 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/FixtureDrivenReducerTests.swift @@ -225,10 +225,6 @@ final class FixtureDrivenReducerTests: XCTestCase { try compareFixture(file: file, fixture: fixture, stateType: ResourceWatchState.self) { state in actions.reduce(state) { resourceWatchReducer(state: $0, action: $1) } } - case "chat": - try compareFixture(file: file, fixture: fixture, stateType: ChatState.self) { state in - actions.reduce(state) { chatReducer(state: $0, action: $1) } - } default: throw FixtureError.unsupportedReducer(fixture.reducer) } diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/NativeReducerTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/NativeReducerTests.swift index 0a81e24a..46d3e555 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/NativeReducerTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/NativeReducerTests.swift @@ -15,15 +15,13 @@ final class NativeReducerTests: XCTestCase { // MARK: - Constants - private let S = "ahp-session:/test-session" - private let C = "ahp-chat:/test-session/default" + private let S = "copilot:/test-session" private let T = "turn-1" // MARK: - Reducers under test private let rootR = AHPRootReducer() private let sessionR = AHPSessionReducer() - private let chatR = AHPChatReducer() // MARK: - Fixtures @@ -41,16 +39,21 @@ final class NativeReducerTests: XCTestCase { modifiedAt: 1000 ), lifecycle: lifecycle, - chats: [] + turns: [] ) } - private func makeChatStateWithActiveTurn() -> ChatState { - ChatState( - resource: C, - title: "Test Chat", - status: .inProgress, - modifiedAt: "1970-01-01T00:00:02.000Z", + private func makeSessionStateWithActiveTurn() -> SessionState { + SessionState( + summary: SessionSummary( + resource: S, + provider: "copilot", + title: "Test Session", + status: .inProgress, + createdAt: 1000, + modifiedAt: 2000 + ), + lifecycle: .ready, turns: [], activeTurn: ActiveTurn( id: T, @@ -66,7 +69,6 @@ final class NativeReducerTests: XCTestCase { func testReducerProtocolConformance() { let _: any Reducer = AHPRootReducer() let _: any Reducer = AHPSessionReducer() - let _: any Reducer = AHPChatReducer() } func testTypeErasure() { @@ -122,17 +124,17 @@ final class NativeReducerTests: XCTestCase { } func testInoutMutationEfficiency() { - var state = makeChatStateWithActiveTurn() + var state = makeSessionStateWithActiveTurn() - chatR.reduce(into: &state, action: .chatResponsePart(ChatResponsePartAction( - type: .chatResponsePart, turnId: T, + sessionR.reduce(into: &state, action: .sessionResponsePart(SessionResponsePartAction( + type: .sessionResponsePart, turnId: T, part: .markdown(MarkdownResponsePart(kind: .markdown, id: "md-1", content: "")) ))) - chatR.reduce(into: &state, action: .chatDelta(ChatDeltaAction( - type: .chatDelta, turnId: T, partId: "md-1", content: "Hello" + sessionR.reduce(into: &state, action: .sessionDelta(SessionDeltaAction( + type: .sessionDelta, turnId: T, partId: "md-1", content: "Hello" ))) - chatR.reduce(into: &state, action: .chatDelta(ChatDeltaAction( - type: .chatDelta, turnId: T, partId: "md-1", content: " World" + sessionR.reduce(into: &state, action: .sessionDelta(SessionDeltaAction( + type: .sessionDelta, turnId: T, partId: "md-1", content: " World" ))) let text = state.activeTurn?.responseParts.compactMap { part in diff --git a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/ReducersTests.swift b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/ReducersTests.swift index 5b8207be..a4583c98 100644 --- a/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/ReducersTests.swift +++ b/clients/swift/AgentHostProtocol/Tests/AgentHostProtocolTests/ReducersTests.swift @@ -13,8 +13,7 @@ final class ReducersTests: XCTestCase { // MARK: - Constants - private let S = "ahp-session:/test-session" - private let C = "ahp-chat:/test-session/default" + private let S = "copilot:/test-session" private let T = "turn-1" // MARK: - Fixtures @@ -37,16 +36,21 @@ final class ReducersTests: XCTestCase { modifiedAt: 1000 ), lifecycle: lifecycle, - chats: [] + turns: [] ) } - private func makeChatStateWithActiveTurn() -> ChatState { - ChatState( - resource: C, - title: "Test Chat", - status: .inProgress, - modifiedAt: "1970-01-01T00:00:02.000Z", + private func makeSessionStateWithActiveTurn() -> SessionState { + SessionState( + summary: SessionSummary( + resource: S, + provider: "copilot", + title: "Test Session", + status: .inProgress, + createdAt: 1000, + modifiedAt: 2000 + ), + lifecycle: .ready, turns: [], activeTurn: ActiveTurn( id: T, @@ -69,21 +73,26 @@ final class ReducersTests: XCTestCase { XCTAssertEqual(state.agents.count, 0) } - func testChatReducerDoesNotMutateTurnsArray() { + func testSessionReducerDoesNotMutateTurnsArray() { let turn1 = Turn(id: "t1", message: Message(text: "First", origin: AnyCodable(["kind": "user"])), responseParts: [], state: .complete) let turn2 = Turn(id: "t2", message: Message(text: "Second", origin: AnyCodable(["kind": "user"])), responseParts: [], state: .complete) let turn3 = Turn(id: "t3", message: Message(text: "Third", origin: AnyCodable(["kind": "user"])), responseParts: [], state: .complete) - let state = ChatState( - resource: C, - title: "T", - status: .idle, - modifiedAt: "1970-01-01T00:00:01.000Z", + let state = SessionState( + summary: SessionSummary( + resource: S, + provider: "copilot", + title: "T", + status: .idle, + createdAt: 1000, + modifiedAt: 1000 + ), + lifecycle: .ready, turns: [turn1, turn2, turn3] ) let original = state.turns - _ = chatReducer( + _ = sessionReducer( state: state, - action: .chatTruncated(ChatTruncatedAction(type: .chatTruncated, turnId: "t1")) + action: .sessionTruncated(SessionTruncatedAction(type: .sessionTruncated, turnId: "t1")) ) XCTAssertEqual(state.turns.count, original.count) } @@ -91,8 +100,8 @@ final class ReducersTests: XCTestCase { // MARK: - Dispatch Validation func testClientDispatchableReturnsTrue() { - let action: StateAction = .chatTurnStarted(ChatTurnStartedAction( - type: .chatTurnStarted, turnId: T, message: Message(text: "Hello", origin: AnyCodable(["kind": "user"])) + let action: StateAction = .sessionTurnStarted(SessionTurnStartedAction( + type: .sessionTurnStarted, turnId: T, message: Message(text: "Hello", origin: AnyCodable(["kind": "user"])) )) XCTAssertTrue(isClientDispatchable(action)) } @@ -104,21 +113,15 @@ final class ReducersTests: XCTestCase { // MARK: - Timestamp Behavior - func testChatTurnStartedUpdatesModifiedAt() { - let state = ChatState( - resource: C, - title: "Test Chat", - status: .idle, - modifiedAt: "1970-01-01T00:00:01.000Z", - turns: [] - ) - let next = chatReducer( + func testTurnStartedUpdatesModifiedAt() { + let state = makeSessionState(lifecycle: .ready) + let next = sessionReducer( state: state, - action: .chatTurnStarted(ChatTurnStartedAction( - type: .chatTurnStarted, turnId: T, message: Message(text: "Hello", origin: AnyCodable(["kind": "user"])) + action: .sessionTurnStarted(SessionTurnStartedAction( + type: .sessionTurnStarted, turnId: T, message: Message(text: "Hello", origin: AnyCodable(["kind": "user"])) )) ) - XCTAssertGreaterThan(next.modifiedAt, state.modifiedAt) + XCTAssertGreaterThan(next.summary.modifiedAt, state.summary.modifiedAt) } func testTitleChangedUpdatesModifiedAt() { diff --git a/clients/swift/CHANGELOG.md b/clients/swift/CHANGELOG.md index 6e5efa08..f53cc391 100644 --- a/clients/swift/CHANGELOG.md +++ b/clients/swift/CHANGELOG.md @@ -38,9 +38,8 @@ the tag matches the version pinned in [`VERSION`](VERSION). resending its entries. Handled by the annotations reducer (no-op on unknown id). -- `ahp-chat:` channel for per-chat conversation state; `SessionState.chats[]` catalog; `SessionState.defaultChat?` input-routing hint; `ChatOrigin` provenance union; `createChat` command. -- `SessionChatAddedAction`, `SessionChatRemovedAction`, and `SessionChatUpdatedAction` handling for incremental chat catalog updates. -- `ChatSummary.workingDirectory` — optional per-chat working directory. Falls back to the session's `workingDirectory` when absent. +### Added + - `RootState` now exposes an optional `_meta` property bag (`meta: [String: AnyCodable]?`) for implementation-defined agent-host metadata, such as a well-known `hostBuild` key carrying the host's build version/commit/date. @@ -54,15 +53,6 @@ the tag matches the version pinned in [`VERSION`](VERSION). remaining gaps (unknown-discriminant response part; the not-yet-implemented annotations channel) pinned by an explicit drift tripwire. -### Changed - -- `ChatState` is now flat — the previous embedded `summary` has been replaced with inlined `resource` / `title` / `status` / `activity` / `modifiedAt` / `model` / `agent` / `origin` / `workingDirectory` properties. `ChatSummary` remains as the standalone catalog entry on `SessionState.chats`. -- `ChatSummary.modifiedAt` and `ChatState.modifiedAt` are now ISO 8601 `String` values instead of `Int64`/`UInt64` milliseconds. - -### Removed - -- `SessionChatsChangedAction` (replaced by the three discrete chat-catalog actions above). - ## [0.3.0] — 2026-06-05 Implements AHP 0.3.0. @@ -108,13 +98,11 @@ Implements AHP 0.3.0. ### Changed -- `fetchTurns` and `completions` now target an `ahp-chat:` channel; `PROTOCOL_VERSION` bumped to `0.4.0`. - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. - Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed -- `SessionState.turns`, `SessionState.activeTurn`, `SessionState.steeringMessage`, `SessionState.queuedMessages`, `SessionState.inputRequests` (moved to `ChatState`). - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. ### Changed diff --git a/clients/typescript/CHANGELOG.md b/clients/typescript/CHANGELOG.md index d359e670..14a92501 100644 --- a/clients/typescript/CHANGELOG.md +++ b/clients/typescript/CHANGELOG.md @@ -38,22 +38,12 @@ hotfix escape hatch. existing annotation's `turnId` / `resource` / `range` / `resolved` without resending its entries. Handled by `annotationsReducer` (no-op on unknown id). -- `ahp-chat:` channel for per-chat conversation state; `SessionState.chats[]` catalog; `SessionState.defaultChat?` input-routing hint; `ChatOrigin` provenance union; `createChat` command. -- `ChatSummary.workingDirectory?` — optional per-chat working directory. Falls back to the session's `workingDirectory` when absent. -- Three discrete chat-catalog actions on the session channel — `SessionChatAddedAction` (upsert by `summary.resource`), `SessionChatRemovedAction`, and `SessionChatUpdatedAction` (partial-update with `Partial`). +### Added + - `RootState` now exposes an optional `_meta` property bag (`_meta?: Record`) for implementation-defined agent-host metadata, such as a well-known `hostBuild` key carrying the host's build version/commit/date. -### Changed - -- `ChatState` is now flat — the previous nested `summary: ChatSummary` has been replaced with inlined `resource` / `title` / `status` / `activity` / `modifiedAt` / `model` / `agent` / `origin` / `workingDirectory` fields. `ChatSummary` remains as the standalone catalog entry on `SessionState.chats`. -- `ChatSummary.modifiedAt` and `ChatState.modifiedAt` are now ISO 8601 `string` values instead of numeric milliseconds. - -### Removed - -- `SessionChatsChangedAction` (replaced by the three discrete chat-catalog actions above). - ## [0.3.0] — 2026-06-05 Implements AHP 0.3.0. @@ -98,13 +88,11 @@ Implements AHP 0.3.0. ### Changed -- `fetchTurns` and `completions` now target an `ahp-chat:` channel; `PROTOCOL_VERSION` bumped to `0.4.0`. - Renamed the `ChangesetSummary` type to `Changeset`. The on-the-wire shape is unchanged. - Moved the `changesets` catalogue from `SessionSummary` to `SessionState`. The `session/changesetsChanged` action now updates `state.changesets` directly instead of `state.summary.changesets`. ### Removed -- `SessionState.turns`, `SessionState.activeTurn`, `SessionState.steeringMessage`, `SessionState.queuedMessages`, `SessionState.inputRequests` (moved to `ChatState`). - Removed the `additions`, `deletions`, and `files` fields from `ChangesetSummary`. Aggregate counts now live on `SessionSummary.changes`; per-changeset views derive their own totals from `ChangesetState.files`. ### Changed diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 6de549bd..6b988f5b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -67,7 +67,6 @@ export default withMermaid(defineConfig({ items: [ { text: 'Root Channel', link: '/specification/root-channel' }, { text: 'Session Channel', link: '/specification/session-channel' }, - { text: 'Chat Channel', link: '/specification/chat-channel' }, { text: 'Terminal Channel', link: '/specification/terminal-channel' }, { text: 'Resource Watch Channel', link: '/specification/resource-watch-channel' }, { text: 'Telemetry Channel', link: '/specification/telemetry-channel' }, @@ -88,7 +87,6 @@ export default withMermaid(defineConfig({ items: [ { text: 'Root Channel', link: '/reference/root' }, { text: 'Session Channel', link: '/reference/session' }, - { text: 'Chat Channel', link: '/reference/chat' }, { text: 'Terminal Channel', link: '/reference/terminal' }, { text: 'Changeset Channel', link: '/reference/changeset' }, { text: 'Annotations Channel', link: '/reference/annotations' }, diff --git a/docs/specification/chat-channel.md b/docs/specification/chat-channel.md deleted file mode 100644 index 03b2efb3..00000000 --- a/docs/specification/chat-channel.md +++ /dev/null @@ -1,137 +0,0 @@ -# Chat Channel - -A chat channel carries the full state of a single conversation thread: turns, streaming responses, tool calls, pending messages, and input requests. A chat always belongs to a [session](./session-channel); a session may contain one or many chats. Chats are independently subscribable so a client can observe a subset of activity without paying the bandwidth cost of every chat in the session. - -## URI - -``` -ahp-chat:/ -``` - -The path is a server-unique identifier (typically a UUID) allocated by the server when the chat is created. The owning session URI is **not** encoded in the chat URI — the relationship is expressed via the session's [`chats`](/reference/session#sessionstate) catalog and each chat's [`origin`](/reference/chat#chatorigin). - -Multiple chat channels may be active simultaneously. Clients subscribe to each chat whose state they want to track. - -## State - -Subscribers receive a [`ChatState`](/reference/chat#chatstate) snapshot. `ChatState` **denormalizes** the [`ChatSummary`](/reference/chat#chatsummary) fields directly onto itself (`resource`, `title`, `status`, `activity`, `modifiedAt`, `model`, `agent`, `origin`, `workingDirectory`) and adds the conversation contents (history of completed turns, the active turn if any, pending messages, outstanding input requests). Producers MUST keep the chat's `ChatSummary` in the session catalog consistent with these inlined fields — typically by dispatching a matching [`session/chatUpdated`](/reference/session#actions) whenever any summary field on the chat changes. Refer to the [State Model guide](/guide/state-model) for a structural overview. - -### Per-chat working directory - -`ChatState.workingDirectory` (and its mirror on [`ChatSummary`](/reference/chat#chatsummary)) is **optional**. When absent, the chat inherits the session's [`workingDirectory`](/reference/session#sessionsummary). Hosts MAY set a per-chat working directory to give individual chats their own filesystem context — for example, allocating a separate git worktree per chat so multiple chats in the same session can make independent edits that the orchestrating chat later merges back. The session-level `workingDirectory` is then the default/primary location for chats that do not override it. - -## Relationship to the session channel - -- A chat's [`ChatSummary`](/reference/chat#chatsummary) appears in the session's [`SessionState.chats`](/reference/session#sessionstate) catalog. The session reducer keeps that catalog in sync with the underlying chat lifecycle. -- The session may also expose [`defaultChat`](/reference/session#sessionstate) as a UI routing hint for input that is addressed to the session as a whole. This is advisory only — chats remain equal peers at the protocol level. -- Session-level fields such as [`status`](/reference/session#sessionsummary), `activity`, and `modifiedAt` are aggregates derived from the session's chats. See the [Session Channel specification](./session-channel#chat-aggregation) for the derivation rules. - -## Lifecycle - -``` -1. Client subscribes to the owning session URI (ahp-session:/) -2. Client (or the server, via a tool call or fork) creates a chat with createChat -3. Server allocates a chat URI (ahp-chat:/) and mutates the session's chats catalog -4. Client subscribes to the chat URI to receive its ChatState snapshot -5. Server streams chat actions over the chat channel until the chat (or its session) is disposed -``` - -### Creation - -[`createChat`](/reference/chat#createchat) is a JSON-RPC request. Callers identify the owning session via the request's `channel` parameter (`ahp-session:/`) and MAY supply: - -- an `initialMessage` to start the first turn immediately, -- per-chat `agent` / `model` / `config` overrides that win over the session defaults, and -- a `source` of type [`ChatForkSource`](/reference/chat#chatforksource) to fork from an existing chat at a specific turn. - -The server allocates the chat URI and adds the chat to the session's catalog (`session/chatAdded` on the session channel) before returning. - -### Origin - -Each chat advertises how it came into existence via [`ChatOrigin`](/reference/chat#chatorigin): - -| Kind | Meaning | -|---|---| -| `user` | User created the chat explicitly (e.g. via the host UI). | -| `fork` | Forked from an existing chat at a specific turn — payload references the source chat URI and turn id. | -| `tool` | Spawned by a tool call running in another chat — payload references the source chat URI and tool call id (e.g. a sub-agent delegation). | - -Clients MAY use the origin to render contextual UI (parent indicators, fork markers, "spawned by tool" badges), but origin is **not** a hierarchy — every chat is equally addressable. - -### Active chat - -Once a chat exists and its session is `lifecycle: 'ready'`, the chat accepts turns. The wire shape mirrors the legacy single-chat session shape: - -- The client dispatches `chat/turnStarted` to begin a turn. -- The server streams `chat/delta`, `chat/responsePart`, `chat/toolCallStart`, `chat/toolCallReady`, and related actions. -- The client dispatches `chat/toolCallConfirmed` / `chat/toolCallResultConfirmed` to approve or deny tool calls, or `chat/turnCancelled` to abort. -- The server dispatches `chat/turnComplete` or `chat/error` when the turn ends. -- The server MAY dispatch `chat/inputRequested` while a turn is active. Clients sync answer drafts with `chat/inputAnswerChanged` and finish the request with `chat/inputCompleted`. - -All actions dispatched on this channel travel on `ActionEnvelope`s whose `channel` is the chat URI. Action payloads do NOT carry their own chat URI — the channel comes from the envelope. - -### Disposal - -A chat is implicitly disposed when its owning session is disposed. The protocol does not currently expose a `disposeChat` command; chats live for the life of their session unless the server prunes them. When a chat is removed (whether explicitly or because its session was torn down), the server MUST update the session's `chats` catalog via `session/chatRemoved` so subscribers can release their per-chat subscriptions. - -## Methods and events on this channel - -This section lists wire methods that are interpreted in the context of a chat URI (`ahp-chat:/`). - -### Commands (`params.channel = "ahp-chat:/"`) - -| Method | Kind | Purpose | -|---|---|---| -| `fetchTurns` | request | Page historical turns for this chat. | -| `completions` | request | Chat-scoped inline completions (e.g. user-message mentions). | - -`createChat` is dispatched against the owning session URI (`params.channel = "ahp-session:/"`). - -### Notifications (`params.channel = "ahp-chat:/"`) - -| Method | Kind | Meaning | -|---|---|---| -| `action` | server → client notification | Chat action envelope (`chat/*` action payloads). | -| `dispatchAction` | client → server notification | Dispatch client actions on this chat (`chat/turnStarted`, `chat/toolCallConfirmed`, ...). | -| `unsubscribe` | client → server notification | Stop receiving messages for this chat channel. | - -## Server Validation of Client Actions - -When the server receives a client-dispatched action on this channel, it MUST validate it before applying. Invalid actions MUST be echoed back with a `rejectionReason` on the `ActionEnvelope`. The validation rules mirror the legacy session validation table — substitute `chat/*` for `session/*`: - -| Action | Condition | Server Behavior | -| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -| Any action referencing a non-existent chat | Channel URI not found | Server MUST silently ignore the action (no echo) | -| `chat/toolCallConfirmed` | Tool call not in `pending-confirmation` state | Server MUST reject the action | -| `chat/turnCancelled` | No active turn | Server MUST reject the action | -| `chat/inputAnswerChanged` | No input request with matching `requestId` | Server SHOULD reject the action | -| `chat/inputAnswerChanged` | `answer.state` requires a value but `answer.value` is absent, or `answer.value.kind` is missing the matching payload field | Server SHOULD reject the action | -| `chat/inputCompleted` | No input request with matching `requestId` | Server SHOULD reject the action | -| `chat/inputCompleted` | `response` is `'accept'` but required questions do not have submitted answers | Server SHOULD reject the action | -| `chat/pendingMessageRemoved` | No pending message with matching `id` and `kind` | Server SHOULD reject the action | - -## Pending Message Consumption - -Pending messages live on the chat, not the session. The consumption rules mirror the legacy session behavior: - -### Queued Messages - -When a turn completes and `queuedMessages` is non-empty, the server SHOULD: - -1. Dispatch `chat/pendingMessageRemoved` with `kind: 'queued'` for the first queued message. -2. Dispatch `chat/turnStarted` with the queued message's `message` and `queuedMessageId` set to the message's `id`. - -When a queued message is added while the chat is idle (no active turn), the server SHOULD immediately consume it using the same two-step sequence. - -### Steering Messages - -When a turn is active and `steeringMessages` is non-empty, the server MAY consume steering messages at its discretion. To consume a steering message, the server: - -1. Dispatches `chat/pendingMessageRemoved` with `kind: 'steering'`. -2. Injects the message content into the model context (the injection mechanism is opaque to the protocol). - -Steering messages added while idle are silently stored and consumed when a turn becomes active. - -## Actions - -Refer to the [Chat Channel Reference](/reference/chat#actions) for the full per-action reference. All chat-scoped action envelopes carry `channel: "ahp-chat:/"`. diff --git a/docs/specification/session-channel.md b/docs/specification/session-channel.md index 0d5578ec..9a8c8069 100644 --- a/docs/specification/session-channel.md +++ b/docs/specification/session-channel.md @@ -1,6 +1,6 @@ # Session Channel -A session channel carries session-level state and acts as the coordination scope for one or more chats. The session tracks lifecycle, model/agent defaults, customizations, per-session configuration, changesets, and the catalog of chats that belong to the session. The per-conversation state — turns, streaming responses, tool calls, pending messages, and input requests — lives on the [chat channel](./chat-channel). +A session channel carries the full state of a single agent conversation: turns, streaming responses, tool calls, pending messages, input requests, customizations, and per-session configuration. One session channel exists per session for as long as the session is alive. ## URI @@ -14,7 +14,7 @@ Multiple session channels may be active simultaneously. Clients subscribe to eac ## State -Subscribers receive a [`SessionState`](/reference/session#sessionstate) snapshot containing the session summary, lifecycle phase, the catalog of [`chats`](/reference/session#sessionstate) belonging to this session, the optional [`defaultChat`](/reference/session#sessionstate) routing hint, model and active-client state, customizations, changesets, and per-session configuration. Per-conversation state (turns, streaming, tool calls, pending messages, input requests) lives on the [chat channel](./chat-channel). Refer to the [State Model guide](/guide/state-model) for a structural overview. +Subscribers receive a [`SessionState`](/reference/session#sessionstate) snapshot containing the session summary, lifecycle phase, history of completed turns, the active turn (if any), pending messages, outstanding input requests, model and active-client state, and other per-session fields. Refer to the [State Model guide](/guide/state-model) for a structural overview. ## Lifecycle @@ -35,42 +35,16 @@ Subscribers receive a [`SessionState`](/reference/session#sessionstate) snapshot ### Active session -Once a session reaches `lifecycle: 'ready'`, clients may create chats on it with [`createChat`](/reference/chat#createchat). Each chat is independently subscribable at its own `ahp-chat:/` URI; see the [Chat Channel specification](./chat-channel) for the per-chat lifecycle, turn flow, tool calls, and input request handling. +Once a session reaches `lifecycle: 'ready'`, it accepts turns: -Session-scoped actions dispatched on this channel are limited to: - -- Catalog mutations — `session/chatAdded`, `session/chatRemoved`, `session/chatUpdated`, and `session/defaultChatChanged`. -- Session-wide configuration — model and agent defaults, active-client tracking, customizations, changesets, lifecycle transitions. +- The client dispatches `session/turnStarted` to begin a turn. +- The server streams `session/delta`, `session/responsePart`, `session/toolCallStart`, `session/toolCallReady`, and related actions. +- The client dispatches `session/toolCallConfirmed` / `session/toolCallResultConfirmed` to approve or deny tool calls, or `session/turnCancelled` to abort. +- The server dispatches `session/turnComplete` or `session/error` when the turn ends. +- The server MAY dispatch `session/inputRequested` while a turn is active. Clients sync answer drafts with `session/inputAnswerChanged` and finish the request with `session/inputCompleted`. All actions dispatched on this channel travel on `ActionEnvelope`s whose `channel` is the session URI. Action payloads do NOT carry their own session URI — the channel comes from the envelope. -### Chat catalog mutations - -Three discrete actions keep `SessionState.chats` in sync as chats come and go. Sessions with a single chat trivially round-trip a `session/chatAdded` once at creation; multi-chat sessions exercise all three: - -| Action | Payload | Reducer behavior | -|---|---|---| -| `session/chatAdded` | `summary: ChatSummary` | Upsert by `summary.resource`. Appends when no entry has the same URI; otherwise replaces the existing entry. Mirrors `root/sessionAdded`. | -| `session/chatRemoved` | `chat: URI` | Removes the matching entry. No-op when no entry matches. If `state.defaultChat` referenced the removed URI, the reducer clears it. Mirrors `root/sessionRemoved`. | -| `session/chatUpdated` | `chat: URI, changes: Partial` | Merges the non-identity fields of `changes` onto the matching entry. No-op when no entry matches; clients SHOULD then wait for a `session/chatAdded`. Identity fields (`resource`) MUST NOT be carried in `changes`. Mirrors `root/sessionSummaryChanged`. | - -The producer of the chat's own [`ChatState`](./chat-channel#state) is responsible for emitting matching `session/chatUpdated` actions so the catalog and the per-chat channel stay consistent. - -### Chat aggregation - -[`SessionSummary`](/reference/session#sessionsummary) carries session-wide identity (`resource`, `provider`, `createdAt`, `workingDirectory`) but several of its mutable fields are aggregates derived from the session's chats. Producers SHOULD apply these rules so clients that only consume the session summary (a session list, for example) still see meaningful state: - -| Field | Derivation rule | -|---|---| -| `status` | Take the activity bits (`Idle` / `InProgress` / `InputNeeded` / `Error`) from the [`defaultChat`](#defaultchat) when set, else from the most recently modified chat. Promote `InputNeeded` if **any** chat needs input. Promote `Error` if **any** chat is in an error state. The orthogonal `IsRead` / `IsArchived` flags remain session-scoped and pass through unchanged. | -| `activity` | Mirror the activity string of the chat that contributes the activity bits — usually the default chat, but the chat that raised `InputNeeded` / `Error` when a non-default chat wins the promotion. | -| `modifiedAt` | The maximum of every chat's `modifiedAt`. | -| `model` / `agent` | The session-level selection. Per-chat overrides are surfaced on individual [`ChatSummary`](/reference/chat#chatsummary) entries; do **not** aggregate them up. | -| `workingDirectory` | The session-level **default**. Individual chats MAY override via [`ChatSummary.workingDirectory`](/reference/chat#chatsummary); aggregating per-chat overrides up is meaningless and SHOULD NOT be attempted. | -| `changes` | Optional roll-up. Producers MAY sum per-chat changeset stats or report the most expensive chat's stats — whichever is cheaper to compute. | - -Sessions with a single chat satisfy all of the above trivially (the chat's values pass through). The rules only matter once a session carries multiple chats. - ### Disposal ```jsonc @@ -95,14 +69,16 @@ session URI (`ahp-session:/`). | Method | Kind | Purpose | |---|---|---| | `createSession` | request | Create a session at the chosen URI. | -| `disposeSession` | request | Dispose this session and its backend resources (cascades to every chat in the session's catalog). | +| `disposeSession` | request | Dispose this session and its backend resources. | +| `fetchTurns` | request | Page historical turns for this session. | +| `completions` | request | Session-scoped inline completions (e.g. user-message mentions). | ### Notifications (`params.channel = "ahp-session:/"`) | Method | Kind | Meaning | |---|---|---| -| `action` | server → client notification | Session action envelope (`session/*` action payloads — catalog updates, lifecycle, model/agent, customizations, changesets). | -| `dispatchAction` | client → server notification | Dispatch client actions on this session (`session/modelChanged`, `session/defaultChatChanged`, ...). | +| `action` | server → client notification | Session action envelope (`session/*` action payloads). | +| `dispatchAction` | client → server notification | Dispatch client actions on this session (`session/turnStarted`, `session/toolCallConfirmed`, ...). | | `unsubscribe` | client → server notification | Stop receiving messages for this session channel. | `auth/required` may also target a session URI when auth is required for an @@ -116,11 +92,37 @@ When the server receives a client-dispatched action on this channel, it MUST val | Action | Condition | Server Behavior | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | Any action referencing a non-existent session | Channel URI not found | Server MUST silently ignore the action (no echo) | -| `session/modelChanged` | A turn is currently active in any chat in this session | Server MUST defer the model change until every active turn completes, then apply it for next turns | -| `session/agentChanged` | A turn is currently active in any chat in this session | Server MUST defer the agent change until every active turn completes, then apply it for next turns | -| `session/defaultChatChanged` | `defaultChat` URI does not match an entry in the session's chat catalog | Server MUST reject the action | +| `session/toolCallConfirmed` | Tool call not in `pending-confirmation` state | Server MUST reject the action | +| `session/turnCancelled` | No active turn | Server MUST reject the action | +| `session/modelChanged` | A turn is currently active | Server MUST defer the model change until the active turn completes, then apply it for the next turn | +| `session/agentChanged` | A turn is currently active | Server MUST defer the agent change until the active turn completes, then apply it for the next turn | +| `session/inputAnswerChanged` | No input request with matching `requestId` | Server SHOULD reject the action | +| `session/inputAnswerChanged` | `answer.state` requires a value but `answer.value` is absent, or `answer.value.kind` is missing the matching payload field | Server SHOULD reject the action | +| `session/inputCompleted` | No input request with matching `requestId` | Server SHOULD reject the action | +| `session/inputCompleted` | `response` is `'accept'` but required questions do not have submitted answers | Server SHOULD reject the action | +| `session/pendingMessageRemoved` | No pending message with matching `id` and `kind` | Server SHOULD reject the action | + +## Pending Message Consumption + +The server consumes pending messages according to their kind: + +### Queued Messages + +When a turn completes and `queuedMessages` is non-empty, the server SHOULD: + +1. Dispatch `session/pendingMessageRemoved` with `kind: 'queued'` for the first queued message. +2. Dispatch `session/turnStarted` with the queued message's `message` and `queuedMessageId` set to the message's `id`. + +When a queued message is added while the session is idle (no active turn), the server SHOULD immediately consume it using the same two-step sequence. + +### Steering Messages + +When a turn is active and `steeringMessages` is non-empty, the server MAY consume steering messages at its discretion. To consume a steering message, the server: + +1. Dispatches `session/pendingMessageRemoved` with `kind: 'steering'`. +2. Injects the message content into the model context (the injection mechanism is opaque to the protocol). -Turn-, tool-call-, input-request-, and pending-message-level validation lives on the [Chat Channel](./chat-channel#server-validation-of-client-actions). +Steering messages added while idle are silently stored and consumed when a turn becomes active. ## Actions diff --git a/schema/actions.schema.json b/schema/actions.schema.json index a1ff1862..b306559a 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -128,6 +128,29 @@ "config" ] }, + "ToolCallActionBase": { + "type": "object", + "description": "Base interface for all tool-call-scoped actions, carrying the common turn\nand tool call identifiers. The owning session URI is identified by the\nenclosing {@link ActionEnvelope}'s `channel` field.", + "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + } + }, + "required": [ + "turnId", + "toolCallId" + ] + }, "SessionReadyAction": { "type": "object", "description": "Session backend initialized successfully.", @@ -157,1084 +180,951 @@ "error" ] }, - "SessionChatAddedAction": { + "SessionTurnStartedAction": { "type": "object", - "description": "A chat was added to this session's catalog. Upsert semantics: if a chat\nwith the same `summary.resource` already exists, the existing entry is\nreplaced.\n\nMirrors the root-channel `root/sessionAdded` notification.", + "description": "A new message has been sent to the agent, and a new turn starts.\n\nA client is only allowed to send {@link MessageKind.User} messages.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionChatAdded" + "$ref": "#/$defs/ActionType.SessionTurnStarted" }, - "summary": { - "$ref": "#/$defs/ChatSummary", - "description": "The full summary of the newly added (or upserted) chat." + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "message": { + "$ref": "#/$defs/Message", + "description": "The new message" + }, + "queuedMessageId": { + "type": "string", + "description": "If this turn was auto-started from a queued message, the ID of that message" } }, "required": [ "type", - "summary" + "turnId", + "message" ] }, - "SessionChatRemovedAction": { + "SessionDeltaAction": { "type": "object", - "description": "A chat was removed from this session's catalog. No-op when no entry matches.\n\nMirrors the root-channel `root/sessionRemoved` notification.", + "description": "Streaming text chunk from the assistant, appended to a specific response part.\n\nThe server MUST first emit a `session/responsePart` to create the target\npart (markdown or reasoning), then use this action to append text to it.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionChatRemoved" + "$ref": "#/$defs/ActionType.SessionDelta" }, - "chat": { - "$ref": "#/$defs/URI", - "description": "The URI of the chat to remove." + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "partId": { + "type": "string", + "description": "Identifier of the response part to append to" + }, + "content": { + "type": "string", + "description": "Text chunk" } }, "required": [ "type", - "chat" + "turnId", + "partId", + "content" ] }, - "SessionChatUpdatedAction": { + "SessionResponsePartAction": { "type": "object", - "description": "One existing chat's summary fields changed.\n\nPartial-update semantics: only fields present in `changes` are written;\nomitted fields are preserved. Identity fields (`resource`) MUST NOT be\ncarried in `changes`. No-op when no entry with `chat` exists — clients\nSHOULD then wait for a {@link SessionChatAddedAction | `session/chatAdded`}.\n\nMirrors the root-channel `root/sessionSummaryChanged` notification.", + "description": "Structured content appended to the response.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionChatUpdated" + "$ref": "#/$defs/ActionType.SessionResponsePart" }, - "chat": { - "$ref": "#/$defs/URI", - "description": "The URI of the chat whose summary changed." + "turnId": { + "type": "string", + "description": "Turn identifier" }, - "changes": { - "type": "object", - "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { - "type": "string", - "description": "Human-readable description of what the chat is currently doing" - }, - "modifiedAt": { - "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nSee {@link ChatState.workingDirectory} for usage notes." - } - }, - "description": "Mutable summary fields that changed; omitted fields are unchanged.\n\nIdentity fields (`resource`) never change and MUST be omitted by\nsenders; receivers SHOULD ignore them if present." + "part": { + "$ref": "#/$defs/ResponsePart", + "description": "Response part (markdown or content ref)" } }, "required": [ "type", - "chat", - "changes" + "turnId", + "part" ] }, - "SessionDefaultChatChangedAction": { + "SessionToolCallStartAction": { "type": "object", - "description": "The default chat input-routing hint for this session changed.", + "description": "A tool call begins — parameters are streaming from the LM.\n\nThe server sets {@link ToolCallContributor | `contributor`} to identify\nthe origin of the tool. For client-provided tools, the named client is\nresponsible for executing the tool once it reaches the `running` state\nand dispatching `session/toolCallComplete`. For MCP-served tools, the\nserver executes the call against the named `McpServerCustomization`.", "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionDefaultChatChanged" + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." }, - "defaultChat": { - "$ref": "#/$defs/URI", - "description": "New default chat URI, or `undefined` to clear the hint." - } - }, - "required": [ - "type" - ] - }, - "SessionTitleChangedAction": { - "type": "object", - "description": "Session title updated. Fired by the server when the title is auto-generated\nfrom conversation, or dispatched by a client to rename a session.", - "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionTitleChanged" + "$ref": "#/$defs/ActionType.SessionToolCallStart" }, - "title": { + "toolName": { "type": "string", - "description": "New title" + "description": "Internal tool name (for debugging/logging)" + }, + "displayName": { + "type": "string", + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called. Absent for\nserver-side tools that are not contributed by a client or MCP server." } }, "required": [ + "turnId", + "toolCallId", "type", - "title" + "toolName", + "displayName" ] }, - "SessionModelChangedAction": { + "SessionToolCallDeltaAction": { "type": "object", - "description": "Model changed for this session.", + "description": "Streaming partial parameters for a tool call.", "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, "type": { - "$ref": "#/$defs/ActionType.SessionModelChanged" + "$ref": "#/$defs/ActionType.SessionToolCallDelta" }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "New model selection" + "content": { + "type": "string", + "description": "Partial parameter content to append" + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Updated progress message" } }, "required": [ + "turnId", + "toolCallId", "type", - "model" + "content" ] }, - "SessionAgentChangedAction": { + "SessionToolCallReadyAction": { "type": "object", - "description": "Custom agent selection changed for this session.\n\nOmitting `agent` (or setting it to `undefined`) clears the selection and\nresets the session to no selected custom agent (provider default behavior).\n\nWhen a turn is currently active, the server MUST defer the change until\nthe active turn completes, then apply it for the next turn (same rule as\n{@link SessionModelChangedAction | `session/modelChanged`}).", + "description": "Tool call parameters are complete, or a running tool requires re-confirmation.\n\nWhen dispatched for a `streaming` tool call, transitions to `pending-confirmation`\nor directly to `running` if `confirmed` is set.\n\nWhen dispatched for a `running` tool call (e.g. mid-execution permission needed),\ntransitions back to `pending-confirmation`. The `invocationMessage` and `_meta`\nSHOULD be updated to describe the specific confirmation needed. Clients use the\nstandard `session/toolCallConfirmed` flow to approve or deny.\n\nFor client-provided tools, the server typically sets `confirmed` to\n`'not-needed'` so the tool transitions directly to `running`, where the\nowning client can begin execution immediately.", "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, "type": { - "$ref": "#/$defs/ActionType.SessionAgentChanged" + "$ref": "#/$defs/ActionType.SessionToolCallReady" }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "New agent selection, or `undefined` to clear the selection and reset the\nsession to no selected custom agent." + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do or what confirmation is needed" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "confirmationTitle": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" + }, + "edits": { + "type": "object", + "properties": { + "items": { + "type": "string" + } + }, + "required": [ + "items" + ], + "description": "File edits that this tool call will perform, for preview before confirmation" + }, + "editable": { + "type": "boolean", + "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "If set, the tool was auto-confirmed and transitions directly to `running`" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/$defs/ConfirmationOption" + }, + "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." } }, "required": [ - "type" + "turnId", + "toolCallId", + "type", + "invocationMessage" ] }, - "SessionIsReadChangedAction": { + "SessionToolCallApprovedAction": { "type": "object", - "description": "The read state of the session changed.\n\nDispatched by a client to mark a session as read (e.g. after viewing it)\nor unread (e.g. after new activity since the client last looked at it).", + "description": "Client approves a pending tool call. The tool transitions to `running`.", "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, "type": { - "$ref": "#/$defs/ActionType.SessionIsReadChanged" + "$ref": "#/$defs/ActionType.SessionToolCallConfirmed" }, - "isRead": { - "type": "boolean", - "description": "Whether the session has been read" + "approved": { + "description": "The tool call was approved" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed" + }, + "editedToolInput": { + "type": "string", + "description": "Edited tool input parameters, if the client modified them before confirming" + }, + "selectedOptionId": { + "type": "string", + "description": "ID of the selected confirmation option, if the server provided options" } }, "required": [ + "turnId", + "toolCallId", "type", - "isRead" + "approved", + "confirmed" ] }, - "SessionIsArchivedChangedAction": { + "SessionToolCallDeniedAction": { "type": "object", - "description": "The archived state of the session changed.\n\nDispatched by a client to archive a session (e.g. the task is\ncomplete) or to unarchive it.", + "description": "Client denies a pending tool call. The tool transitions to `cancelled`.\n\nFor client-provided tools, the owning client MUST dispatch this if it does\nnot recognize the tool or cannot execute it.", "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionIsArchivedChanged" - }, - "isArchived": { - "type": "boolean", - "description": "Whether the session is archived" + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, + "type": { + "$ref": "#/$defs/ActionType.SessionToolCallConfirmed" + }, + "approved": { + "description": "The tool call was denied" + }, + "reason": { + "oneOf": [ + { + "$ref": "#/$defs/ToolCallCancellationReason.Denied" + }, + { + "$ref": "#/$defs/ToolCallCancellationReason.Skipped" + } + ], + "description": "Why the tool was cancelled" + }, + "userSuggestion": { + "$ref": "#/$defs/Message", + "description": "What the user suggested doing instead" + }, + "reasonMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Optional explanation for the denial" + }, + "selectedOptionId": { + "type": "string", + "description": "ID of the selected confirmation option, if the server provided options" } }, "required": [ + "turnId", + "toolCallId", "type", - "isArchived" + "approved", + "reason" ] }, - "SessionActivityChangedAction": { + "SessionToolCallCompleteAction": { "type": "object", - "description": "The activity description of the session changed.\n\nDispatched by the server to indicate what the session is currently doing\n(e.g. running a tool, thinking). Clear activity by setting it to `undefined`.", + "description": "Tool execution finished. Transitions to `completed` or `pending-result-confirmation`\nif `requiresResultConfirmation` is `true`.\n\nFor client-provided tools (where `toolClientId` is set on the tool call state),\nthe owning client dispatches this action with the execution result. The server\nSHOULD reject this action if the dispatching client does not match `toolClientId`.\n\nServers waiting on a client tool call MAY time out after a reasonable duration\nif the implementing client disconnects or becomes unresponsive, and dispatch\nthis action with `result.success = false` and an appropriate error.", "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionActivityChanged" + "turnId": { + "type": "string", + "description": "Turn identifier" }, - "activity": { + "toolCallId": { "type": "string", - "description": "Human-readable description of current activity, or `undefined` to clear" + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, + "type": { + "$ref": "#/$defs/ActionType.SessionToolCallComplete" + }, + "result": { + "$ref": "#/$defs/ToolCallResult", + "description": "Execution result" + }, + "requiresResultConfirmation": { + "type": "boolean", + "description": "If true, the result requires client approval before finalizing" } }, "required": [ + "turnId", + "toolCallId", "type", - "activity" + "result" ] }, - "SessionChangesetsChangedAction": { + "SessionToolCallResultConfirmedAction": { "type": "object", - "description": "The {@link Changeset | catalogue of changesets} the agent host\nadvertises for this session changed. Replaces\n{@link SessionState.changesets | `state.changesets`} entirely\n(full-replacement semantics) — set to `undefined` to clear the\ncatalogue.\n\nProducers dispatch this whenever entries are added or removed. The\nfan-out happens through this action so observers see catalogue\nmutations in the same {@link ChangesetAction | per-changeset} action\nstream they already follow for file-level updates.", + "description": "Client approves or denies a tool's result.\n\nIf `approved` is `false`, the tool transitions to `cancelled` with reason `result-denied`.", "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, "type": { - "$ref": "#/$defs/ActionType.SessionChangesetsChanged" + "$ref": "#/$defs/ActionType.SessionToolCallResultConfirmed" }, - "changesets": { - "type": "array", - "items": { - "$ref": "#/$defs/Changeset" - }, - "description": "New catalogue, or `undefined` to clear it" + "approved": { + "type": "boolean", + "description": "Whether the result was approved" } }, "required": [ + "turnId", + "toolCallId", "type", - "changesets" + "approved" ] }, - "SessionServerToolsChangedAction": { + "SessionToolCallContentChangedAction": { "type": "object", - "description": "Server tools for this session have changed.\n\nFull-replacement semantics: the `tools` array replaces the previous `serverTools` entirely.", + "description": "Partial content produced while a tool is still executing.\n\nReplaces the `content` array on the running tool call state. Clients can\nuse this to display live feedback (e.g. a terminal reference) before the\ntool completes.\n\nFor client-provided tools (where `toolClientId` is set on the tool call state),\nthe owning client dispatches this action to stream intermediate content while\nexecuting. The server SHOULD reject this action if the dispatching client does\nnot match `toolClientId`.", "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, "type": { - "$ref": "#/$defs/ActionType.SessionServerToolsChanged" + "$ref": "#/$defs/ActionType.SessionToolCallContentChanged" }, - "tools": { + "content": { "type": "array", "items": { - "$ref": "#/$defs/ToolDefinition" + "$ref": "#/$defs/ToolResultContent" }, - "description": "Updated server tools list (full replacement)" + "description": "The current partial content for the running tool call" } }, "required": [ + "turnId", + "toolCallId", "type", - "tools" + "content" ] }, - "SessionActiveClientChangedAction": { + "SessionTurnCompleteAction": { "type": "object", - "description": "The active client for this session has changed.\n\nA client dispatches this action with its own `SessionActiveClient` to claim\nthe active role, or with `null` to release it. The server SHOULD reject if\nanother client is already active. The server SHOULD automatically dispatch\nthis action with `activeClient: null` when the active client disconnects.", + "description": "Turn finished — the assistant is idle.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionActiveClientChanged" + "$ref": "#/$defs/ActionType.SessionTurnComplete" }, - "activeClient": { - "oneOf": [ - { - "$ref": "#/$defs/SessionActiveClient" - }, - {} - ], - "description": "The new active client, or `null` to unset" + "turnId": { + "type": "string", + "description": "Turn identifier" } }, "required": [ "type", - "activeClient" + "turnId" ] }, - "SessionActiveClientToolsChangedAction": { + "SessionTurnCancelledAction": { "type": "object", - "description": "The active client's tool list has changed.\n\nFull-replacement semantics: the `tools` array replaces the active client's\nprevious tools entirely. The server SHOULD reject if the dispatching client\nis not the current active client.", + "description": "Turn was aborted; server stops processing.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionActiveClientToolsChanged" + "$ref": "#/$defs/ActionType.SessionTurnCancelled" }, - "tools": { - "type": "array", - "items": { - "$ref": "#/$defs/ToolDefinition" - }, - "description": "Updated client tools list (full replacement)" + "turnId": { + "type": "string", + "description": "Turn identifier" } }, "required": [ "type", - "tools" + "turnId" ] }, - "SessionCustomizationsChangedAction": { + "SessionErrorAction": { "type": "object", - "description": "The session's customizations have changed.\n\nFull-replacement semantics: the `customizations` array replaces the\nprevious `customizations` entirely.", + "description": "Error during turn processing.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionCustomizationsChanged" + "$ref": "#/$defs/ActionType.SessionError" }, - "customizations": { - "type": "array", - "items": { - "$ref": "#/$defs/Customization" - }, - "description": "Updated customization list (full replacement)." + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details" } }, "required": [ "type", - "customizations" + "turnId", + "error" ] }, - "SessionCustomizationToggledAction": { + "SessionTitleChangedAction": { "type": "object", - "description": "A client toggled a container customization on or off.\n\nTargets a top-level container (plugin or directory) by `id`. Only\ncontainers have an `enabled` flag; children are always active when\ntheir container is enabled. Is a no-op when no matching container is\nfound.", + "description": "Session title updated. Fired by the server when the title is auto-generated\nfrom conversation, or dispatched by a client to rename a session.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionCustomizationToggled" + "$ref": "#/$defs/ActionType.SessionTitleChanged" }, - "id": { + "title": { "type": "string", - "description": "The id of the container to toggle." - }, - "enabled": { - "type": "boolean", - "description": "Whether to enable or disable the container." + "description": "New title" } }, "required": [ "type", - "id", - "enabled" + "title" ] }, - "SessionCustomizationUpdatedAction": { + "SessionUsageAction": { "type": "object", - "description": "Upserts a top-level customization (plugin or directory).\n\nThe reducer locates the existing entry by `customization.id`:\n\n- If found, the entry is replaced entirely with `customization`,\n including its `children` array. To preserve existing children, the\n host must include them on the payload.\n- If not found, the entry is appended.", + "description": "Token usage report for a turn.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionCustomizationUpdated" + "$ref": "#/$defs/ActionType.SessionUsage" }, - "customization": { - "$ref": "#/$defs/Customization", - "description": "The customization to upsert (matched by `customization.id`)." + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage data" } }, "required": [ "type", - "customization" + "turnId", + "usage" ] }, - "SessionCustomizationRemovedAction": { + "SessionReasoningAction": { "type": "object", - "description": "Removes a customization by id.\n\nSearches every container and its children for the entry. If the entry\nis a container, its children are removed with it. Is a no-op when no\nmatching id is found.", + "description": "Reasoning/thinking text from the model, appended to a specific reasoning response part.\n\nThe server MUST first emit a `session/responsePart` to create the target\nreasoning part, then use this action to append text to it.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionCustomizationRemoved" + "$ref": "#/$defs/ActionType.SessionReasoning" }, - "id": { + "turnId": { "type": "string", - "description": "The id of the customization to remove." + "description": "Turn identifier" + }, + "partId": { + "type": "string", + "description": "Identifier of the reasoning response part to append to" + }, + "content": { + "type": "string", + "description": "Reasoning text chunk" } }, "required": [ "type", - "id" + "turnId", + "partId", + "content" ] }, - "SessionMcpServerStateChangedAction": { + "SessionModelChangedAction": { "type": "object", - "description": "Updates the runtime fields of an existing\n{@link McpServerCustomization} — narrow alternative to\n{@link SessionCustomizationUpdatedAction} for the high-frequency\n`starting` ↔ `ready` ↔ `authRequired` transitions.\n\nLocates the target entry by `id`, searching both the top-level\ncustomization list and the `children` array of every container.\nReplaces the entry's {@link McpServerCustomization.state | `state`}\nand {@link McpServerCustomization.channel | `channel`}\n(full-replacement semantics: omit `channel` to clear an existing\nchannel URI). Other fields of the customization are preserved.\n\nIs a no-op when no matching `McpServerCustomization` is found. To\nupdate any other field (name, icons, `mcpApp` capabilities, etc.) use\n{@link SessionCustomizationUpdatedAction} instead.\n\nWhen the transition is to {@link McpServerStatus.AuthRequired}\nbecause of a request issued mid-turn, the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session — see\n{@link McpServerAuthRequiredState} for the rationale.", + "description": "Model changed for this session.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionMcpServerStateChanged" - }, - "id": { - "type": "string", - "description": "The id of the {@link McpServerCustomization} to update." - }, - "state": { - "$ref": "#/$defs/McpServerState", - "description": "The new lifecycle state." + "$ref": "#/$defs/ActionType.SessionModelChanged" }, - "channel": { - "$ref": "#/$defs/URI", - "description": "Updated `mcp://` side-channel URI. Full-replacement: omit to clear\nan existing channel (typical when leaving\n{@link McpServerStatus.Ready | `Ready`})." + "model": { + "$ref": "#/$defs/ModelSelection", + "description": "New model selection" } }, "required": [ "type", - "id", - "state" + "model" ] }, - "SessionConfigChangedAction": { + "SessionAgentChangedAction": { "type": "object", - "description": "Client changed a mutable config value mid-session.\n\nOnly properties with `sessionMutable: true` in the config schema may be\nchanged. The server validates and broadcasts the action; the reducer merges\nthe new values into `state.config.values`.", + "description": "Custom agent selection changed for this session.\n\nOmitting `agent` (or setting it to `undefined`) clears the selection and\nresets the session to no selected custom agent (provider default behavior).\n\nWhen a turn is currently active, the server MUST defer the change until\nthe active turn completes, then apply it for the next turn (same rule as\n{@link SessionModelChangedAction | `session/modelChanged`}).", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionConfigChanged" + "$ref": "#/$defs/ActionType.SessionAgentChanged" }, - "config": { - "type": "object", - "additionalProperties": {}, - "description": "Updated config values" + "agent": { + "$ref": "#/$defs/AgentSelection", + "description": "New agent selection, or `undefined` to clear the selection and reset the\nsession to no selected custom agent." + } + }, + "required": [ + "type" + ] + }, + "SessionIsReadChangedAction": { + "type": "object", + "description": "The read state of the session changed.\n\nDispatched by a client to mark a session as read (e.g. after viewing it)\nor unread (e.g. after new activity since the client last looked at it).", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.SessionIsReadChanged" }, - "replace": { + "isRead": { "type": "boolean", - "description": "When `true`, replaces all config values instead of merging" + "description": "Whether the session has been read" } }, "required": [ "type", - "config" + "isRead" ] }, - "SessionMetaChangedAction": { + "SessionIsArchivedChangedAction": { "type": "object", - "description": "The session's `_meta` side-channel changed. Replaces `state._meta`\nentirely (full-replacement semantics). Producers SHOULD merge any\nkeys they wish to preserve into the new value before dispatching.", + "description": "The archived state of the session changed.\n\nDispatched by a client to archive a session (e.g. the task is\ncomplete) or to unarchive it.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionMetaChanged" + "$ref": "#/$defs/ActionType.SessionIsArchivedChanged" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "New `_meta` payload, or `undefined` to clear it" + "isArchived": { + "type": "boolean", + "description": "Whether the session is archived" } }, "required": [ "type", - "_meta" + "isArchived" ] }, - "ToolCallActionBase": { + "SessionActivityChangedAction": { "type": "object", - "description": "Base interface for all tool-call-scoped actions, carrying the common turn\nand tool call identifiers. The owning chat URI is identified by the\nenclosing {@link ActionEnvelope}'s `channel` field.", + "description": "The activity description of the session changed.\n\nDispatched by the server to indicate what the session is currently doing\n(e.g. running a tool, thinking). Clear activity by setting it to `undefined`.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" + "type": { + "$ref": "#/$defs/ActionType.SessionActivityChanged" }, - "toolCallId": { + "activity": { "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + "description": "Human-readable description of current activity, or `undefined` to clear" } }, "required": [ - "turnId", - "toolCallId" + "type", + "activity" ] }, - "ChatTurnStartedAction": { + "SessionChangesetsChangedAction": { "type": "object", - "description": "A new message has been sent to the agent, and a new turn starts.\n\nA client is only allowed to send {@link MessageKind.User} messages.", + "description": "The {@link Changeset | catalogue of changesets} the agent host\nadvertises for this session changed. Replaces\n{@link SessionState.changesets | `state.changesets`} entirely\n(full-replacement semantics) — set to `undefined` to clear the\ncatalogue.\n\nProducers dispatch this whenever entries are added or removed. The\nfan-out happens through this action so observers see catalogue\nmutations in the same {@link ChangesetAction | per-changeset} action\nstream they already follow for file-level updates.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatTurnStarted" - }, - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "message": { - "$ref": "#/$defs/Message", - "description": "The new message" + "$ref": "#/$defs/ActionType.SessionChangesetsChanged" }, - "queuedMessageId": { - "type": "string", - "description": "If this turn was auto-started from a queued message, the ID of that message" + "changesets": { + "type": "array", + "items": { + "$ref": "#/$defs/Changeset" + }, + "description": "New catalogue, or `undefined` to clear it" } }, "required": [ "type", - "turnId", - "message" + "changesets" ] }, - "ChatDeltaAction": { + "SessionServerToolsChangedAction": { "type": "object", - "description": "Streaming text chunk from the assistant, appended to a specific response part.\n\nThe server MUST first emit a `chat/responsePart` to create the target\npart (markdown or reasoning), then use this action to append text to it.", + "description": "Server tools for this session have changed.\n\nFull-replacement semantics: the `tools` array replaces the previous `serverTools` entirely.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatDelta" - }, - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "partId": { - "type": "string", - "description": "Identifier of the response part to append to" + "$ref": "#/$defs/ActionType.SessionServerToolsChanged" }, - "content": { - "type": "string", - "description": "Text chunk" + "tools": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolDefinition" + }, + "description": "Updated server tools list (full replacement)" } }, "required": [ "type", - "turnId", - "partId", - "content" + "tools" ] }, - "ChatResponsePartAction": { + "SessionActiveClientChangedAction": { "type": "object", - "description": "Structured content appended to the response.", + "description": "The active client for this session has changed.\n\nA client dispatches this action with its own `SessionActiveClient` to claim\nthe active role, or with `null` to release it. The server SHOULD reject if\nanother client is already active. The server SHOULD automatically dispatch\nthis action with `activeClient: null` when the active client disconnects.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatResponsePart" - }, - "turnId": { - "type": "string", - "description": "Turn identifier" + "$ref": "#/$defs/ActionType.SessionActiveClientChanged" }, - "part": { - "$ref": "#/$defs/ResponsePart", - "description": "Response part (markdown or content ref)" + "activeClient": { + "oneOf": [ + { + "$ref": "#/$defs/SessionActiveClient" + }, + {} + ], + "description": "The new active client, or `null` to unset" } }, "required": [ "type", - "turnId", - "part" + "activeClient" ] }, - "ChatToolCallStartAction": { + "SessionActiveClientToolsChangedAction": { "type": "object", - "description": "A tool call begins — parameters are streaming from the LM.\n\nThe server sets {@link ToolCallContributor | `contributor`} to identify\nthe origin of the tool. For client-provided tools, the named client is\nresponsible for executing the tool once it reaches the `running` state\nand dispatching `chat/toolCallComplete`. For MCP-served tools, the\nserver executes the call against the named `McpServerCustomization`.", + "description": "The active client's tool list has changed.\n\nFull-replacement semantics: the `tools` array replaces the active client's\nprevious tools entirely. The server SHOULD reject if the dispatching client\nis not the current active client.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallStart" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" - }, - "displayName": { - "type": "string", - "description": "Human-readable tool name" + "$ref": "#/$defs/ActionType.SessionActiveClientToolsChanged" }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called. Absent for\nserver-side tools that are not contributed by a client or MCP server." + "tools": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolDefinition" + }, + "description": "Updated client tools list (full replacement)" } }, "required": [ - "turnId", - "toolCallId", "type", - "toolName", - "displayName" + "tools" ] }, - "ChatToolCallDeltaAction": { + "SessionCustomizationsChangedAction": { "type": "object", - "description": "Streaming partial parameters for a tool call.", + "description": "The session's customizations have changed.\n\nFull-replacement semantics: the `customizations` array replaces the\nprevious `customizations` entirely.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallDelta" - }, - "content": { - "type": "string", - "description": "Partial parameter content to append" + "$ref": "#/$defs/ActionType.SessionCustomizationsChanged" }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Updated progress message" + "customizations": { + "type": "array", + "items": { + "$ref": "#/$defs/Customization" + }, + "description": "Updated customization list (full replacement)." } }, "required": [ - "turnId", - "toolCallId", "type", - "content" + "customizations" ] }, - "ChatToolCallReadyAction": { + "SessionCustomizationToggledAction": { "type": "object", - "description": "Tool call parameters are complete, or a running tool requires re-confirmation.\n\nWhen dispatched for a `streaming` tool call, transitions to `pending-confirmation`\nor directly to `running` if `confirmed` is set.\n\nWhen dispatched for a `running` tool call (e.g. mid-execution permission needed),\ntransitions back to `pending-confirmation`. The `invocationMessage` and `_meta`\nSHOULD be updated to describe the specific confirmation needed. Clients use the\nstandard `chat/toolCallConfirmed` flow to approve or deny.\n\nFor client-provided tools, the server typically sets `confirmed` to\n`'not-needed'` so the tool transitions directly to `running`, where the\nowning client can begin execution immediately.", + "description": "A client toggled a container customization on or off.\n\nTargets a top-level container (plugin or directory) by `id`. Only\ncontainers have an `enabled` flag; children are always active when\ntheir container is enabled. Is a no-op when no matching container is\nfound.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" + "type": { + "$ref": "#/$defs/ActionType.SessionCustomizationToggled" }, - "toolCallId": { + "id": { "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + "description": "The id of the container to toggle." }, + "enabled": { + "type": "boolean", + "description": "Whether to enable or disable the container." + } + }, + "required": [ + "type", + "id", + "enabled" + ] + }, + "SessionCustomizationUpdatedAction": { + "type": "object", + "description": "Upserts a top-level customization (plugin or directory).\n\nThe reducer locates the existing entry by `customization.id`:\n\n- If found, the entry is replaced entirely with `customization`,\n including its `children` array. To preserve existing children, the\n host must include them on the payload.\n- If not found, the entry is appended.", + "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatToolCallReady" - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do or what confirmation is needed" + "$ref": "#/$defs/ActionType.SessionCustomizationUpdated" }, - "toolInput": { - "type": "string", - "description": "Raw tool input" - }, - "confirmationTitle": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" - }, - "edits": { - "type": "object", - "properties": { - "items": { - "type": "string" - } - }, - "required": [ - "items" - ], - "description": "File edits that this tool call will perform, for preview before confirmation" - }, - "editable": { - "type": "boolean", - "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "If set, the tool was auto-confirmed and transitions directly to `running`" - }, - "options": { - "type": "array", - "items": { - "$ref": "#/$defs/ConfirmationOption" - }, - "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." + "customization": { + "$ref": "#/$defs/Customization", + "description": "The customization to upsert (matched by `customization.id`)." } }, "required": [ - "turnId", - "toolCallId", "type", - "invocationMessage" + "customization" ] }, - "ChatToolCallApprovedAction": { + "SessionCustomizationRemovedAction": { "type": "object", - "description": "Client approves a pending tool call. The tool transitions to `running`.", + "description": "Removes a customization by id.\n\nSearches every container and its children for the entry. If the entry\nis a container, its children are removed with it. Is a no-op when no\nmatching id is found.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallConfirmed" - }, - "approved": { - "description": "The tool call was approved" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed" - }, - "editedToolInput": { - "type": "string", - "description": "Edited tool input parameters, if the client modified them before confirming" + "$ref": "#/$defs/ActionType.SessionCustomizationRemoved" }, - "selectedOptionId": { + "id": { "type": "string", - "description": "ID of the selected confirmation option, if the server provided options" + "description": "The id of the customization to remove." } }, "required": [ - "turnId", - "toolCallId", "type", - "approved", - "confirmed" + "id" ] }, - "ChatToolCallDeniedAction": { + "SessionMcpServerStateChangedAction": { "type": "object", - "description": "Client denies a pending tool call. The tool transitions to `cancelled`.\n\nFor client-provided tools, the owning client MUST dispatch this if it does\nnot recognize the tool or cannot execute it.", + "description": "Updates the runtime fields of an existing\n{@link McpServerCustomization} — narrow alternative to\n{@link SessionCustomizationUpdatedAction} for the high-frequency\n`starting` ↔ `ready` ↔ `authRequired` transitions.\n\nLocates the target entry by `id`, searching both the top-level\ncustomization list and the `children` array of every container.\nReplaces the entry's {@link McpServerCustomization.state | `state`}\nand {@link McpServerCustomization.channel | `channel`}\n(full-replacement semantics: omit `channel` to clear an existing\nchannel URI). Other fields of the customization are preserved.\n\nIs a no-op when no matching `McpServerCustomization` is found. To\nupdate any other field (name, icons, `mcpApp` capabilities, etc.) use\n{@link SessionCustomizationUpdatedAction} instead.\n\nWhen the transition is to {@link McpServerStatus.AuthRequired}\nbecause of a request issued mid-turn, the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session — see\n{@link McpServerAuthRequiredState} for the rationale.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallConfirmed" - }, - "approved": { - "description": "The tool call was denied" - }, - "reason": { - "oneOf": [ - { - "$ref": "#/$defs/ToolCallCancellationReason.Denied" - }, - { - "$ref": "#/$defs/ToolCallCancellationReason.Skipped" - } - ], - "description": "Why the tool was cancelled" + "$ref": "#/$defs/ActionType.SessionMcpServerStateChanged" }, - "userSuggestion": { - "$ref": "#/$defs/Message", - "description": "What the user suggested doing instead" + "id": { + "type": "string", + "description": "The id of the {@link McpServerCustomization} to update." }, - "reasonMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Optional explanation for the denial" + "state": { + "$ref": "#/$defs/McpServerState", + "description": "The new lifecycle state." }, - "selectedOptionId": { - "type": "string", - "description": "ID of the selected confirmation option, if the server provided options" + "channel": { + "$ref": "#/$defs/URI", + "description": "Updated `mcp://` side-channel URI. Full-replacement: omit to clear\nan existing channel (typical when leaving\n{@link McpServerStatus.Ready | `Ready`})." } }, "required": [ - "turnId", - "toolCallId", "type", - "approved", - "reason" + "id", + "state" ] }, - "ChatToolCallCompleteAction": { + "SessionConfigChangedAction": { "type": "object", - "description": "Tool execution finished. Transitions to `completed` or `pending-result-confirmation`\nif `requiresResultConfirmation` is `true`.\n\nFor client-provided tools (where `toolClientId` is set on the tool call state),\nthe owning client dispatches this action with the execution result. The server\nSHOULD reject this action if the dispatching client does not match `toolClientId`.\n\nServers waiting on a client tool call MAY time out after a reasonable duration\nif the implementing client disconnects or becomes unresponsive, and dispatch\nthis action with `result.success = false` and an appropriate error.", + "description": "Client changed a mutable config value mid-session.\n\nOnly properties with `sessionMutable: true` in the config schema may be\nchanged. The server validates and broadcasts the action; the reducer merges\nthe new values into `state.config.values`.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" + "type": { + "$ref": "#/$defs/ActionType.SessionConfigChanged" }, - "_meta": { + "config": { "type": "object", "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, - "type": { - "$ref": "#/$defs/ActionType.ChatToolCallComplete" - }, - "result": { - "$ref": "#/$defs/ToolCallResult", - "description": "Execution result" + "description": "Updated config values" }, - "requiresResultConfirmation": { + "replace": { "type": "boolean", - "description": "If true, the result requires client approval before finalizing" + "description": "When `true`, replaces all config values instead of merging" } }, "required": [ - "turnId", - "toolCallId", "type", - "result" + "config" ] }, - "ChatToolCallResultConfirmedAction": { + "SessionMetaChangedAction": { "type": "object", - "description": "Client approves or denies a tool's result.\n\nIf `approved` is `false`, the tool transitions to `cancelled` with reason `result-denied`.", + "description": "The session's `_meta` side-channel changed. Replaces `state._meta`\nentirely (full-replacement semantics). Producers SHOULD merge any\nkeys they wish to preserve into the new value before dispatching.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" + "type": { + "$ref": "#/$defs/ActionType.SessionMetaChanged" }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, - "type": { - "$ref": "#/$defs/ActionType.ChatToolCallResultConfirmed" - }, - "approved": { - "type": "boolean", - "description": "Whether the result was approved" + "description": "New `_meta` payload, or `undefined` to clear it" } }, "required": [ - "turnId", - "toolCallId", "type", - "approved" + "_meta" ] }, - "ChatToolCallContentChangedAction": { + "SessionTruncatedAction": { "type": "object", - "description": "Partial content produced while a tool is still executing.\n\nReplaces the `content` array on the running tool call state. Clients can\nuse this to display live feedback (e.g. a terminal reference) before the\ntool completes.\n\nFor client-provided tools (where `toolClientId` is set on the tool call state),\nthe owning client dispatches this action to stream intermediate content while\nexecuting. The server SHOULD reject this action if the dispatching client does\nnot match `toolClientId`.", + "description": "Truncates a session's history. If `turnId` is provided, all turns after that\nturn are removed and the specified turn is kept. If `turnId` is omitted, all\nturns are removed.\n\nIf there is an active turn it is silently dropped and the session status\nreturns to `idle`.\n\nCommon use-case: truncate old data then dispatch a new\n`session/turnStarted` with an edited message.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" + "type": { + "$ref": "#/$defs/ActionType.SessionTruncated" }, - "toolCallId": { + "turnId": { "type": "string", - "description": "Tool call identifier" + "description": "Keep turns up to and including this turn. Omit to clear all turns." + } + }, + "required": [ + "type" + ] + }, + "SessionPendingMessageSetAction": { + "type": "object", + "description": "A pending message was set (upsert semantics: creates or replaces).\n\nFor steering messages, this always replaces the single steering message.\nFor queued messages, if a message with the given `id` already exists it is\nupdated in place; otherwise it is appended to the queue. If the session is\nidle when a queued message is set, the server SHOULD immediately consume it\nand start a new turn.\n\nA client is only allowed to send {@link MessageKind.User} messages.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.SessionPendingMessageSet" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + "kind": { + "$ref": "#/$defs/PendingMessageKind", + "description": "Whether this is a steering or queued message" }, - "type": { - "$ref": "#/$defs/ActionType.ChatToolCallContentChanged" + "id": { + "type": "string", + "description": "Unique identifier for this pending message" }, - "content": { - "type": "array", - "items": { - "$ref": "#/$defs/ToolResultContent" - }, - "description": "The current partial content for the running tool call" + "message": { + "$ref": "#/$defs/Message", + "description": "The message content" } }, "required": [ - "turnId", - "toolCallId", "type", - "content" + "kind", + "id", + "message" ] }, - "ChatTurnCompleteAction": { + "SessionPendingMessageRemovedAction": { "type": "object", - "description": "Turn finished — the assistant is idle.", + "description": "A pending message was removed (steering or queued).\n\nDispatched by clients to cancel a pending message, or by the server when\nit consumes a message (e.g. starting a turn from a queued message or\ninjecting a steering message into the current turn).", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatTurnComplete" + "$ref": "#/$defs/ActionType.SessionPendingMessageRemoved" }, - "turnId": { + "kind": { + "$ref": "#/$defs/PendingMessageKind", + "description": "Whether this is a steering or queued message" + }, + "id": { "type": "string", - "description": "Turn identifier" + "description": "Identifier of the pending message to remove" } }, "required": [ "type", - "turnId" + "kind", + "id" ] }, - "ChatTurnCancelledAction": { + "SessionQueuedMessagesReorderedAction": { "type": "object", - "description": "Turn was aborted; server stops processing.", + "description": "Reorder the queued messages.\n\nThe `order` array contains the IDs of queued messages in their new\ndesired order. IDs not present in the current queue are ignored.\nQueued messages whose IDs are absent from `order` are appended at\nthe end in their original relative order (so a client with a stale\nview of the queue never silently drops messages).", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatTurnCancelled" + "$ref": "#/$defs/ActionType.SessionQueuedMessagesReordered" }, - "turnId": { - "type": "string", - "description": "Turn identifier" + "order": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Queued message IDs in the desired order" } }, "required": [ "type", - "turnId" + "order" ] }, - "ChatErrorAction": { + "SessionInputRequestedAction": { "type": "object", - "description": "Error during turn processing.", + "description": "A session requested input from the user.\n\nFull-request upsert semantics: the `request` replaces any existing request\nwith the same `id`, or is appended if it is new. Answer drafts are preserved\nunless `request.answers` is provided.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatError" - }, - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details" - } - }, - "required": [ - "type", - "turnId", - "error" - ] - }, - "ChatUsageAction": { - "type": "object", - "description": "Token usage report for a turn.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.ChatUsage" - }, - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage data" - } - }, - "required": [ - "type", - "turnId", - "usage" - ] - }, - "ChatReasoningAction": { - "type": "object", - "description": "Reasoning/thinking text from the model, appended to a specific reasoning response part.\n\nThe server MUST first emit a `chat/responsePart` to create the target\nreasoning part, then use this action to append text to it.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.ChatReasoning" - }, - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "partId": { - "type": "string", - "description": "Identifier of the reasoning response part to append to" - }, - "content": { - "type": "string", - "description": "Reasoning text chunk" - } - }, - "required": [ - "type", - "turnId", - "partId", - "content" - ] - }, - "ChatTruncatedAction": { - "type": "object", - "description": "Truncates a session's history. If `turnId` is provided, all turns after that\nturn are removed and the specified turn is kept. If `turnId` is omitted, all\nturns are removed.\n\nIf there is an active turn it is silently dropped and the chat status\nreturns to `idle`.\n\nCommon use-case: truncate old data then dispatch a new\n`chat/turnStarted` with an edited message.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.ChatTruncated" - }, - "turnId": { - "type": "string", - "description": "Keep turns up to and including this turn. Omit to clear all turns." - } - }, - "required": [ - "type" - ] - }, - "ChatPendingMessageSetAction": { - "type": "object", - "description": "A pending message was set (upsert semantics: creates or replaces).\n\nFor steering messages, this always replaces the single steering message.\nFor queued messages, if a message with the given `id` already exists it is\nupdated in place; otherwise it is appended to the queue. If the chat is\nidle when a queued message is set, the server SHOULD immediately consume it\nand start a new turn.\n\nA client is only allowed to send {@link MessageKind.User} messages.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.ChatPendingMessageSet" - }, - "kind": { - "$ref": "#/$defs/PendingMessageKind", - "description": "Whether this is a steering or queued message" - }, - "id": { - "type": "string", - "description": "Unique identifier for this pending message" - }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message content" - } - }, - "required": [ - "type", - "kind", - "id", - "message" - ] - }, - "ChatPendingMessageRemovedAction": { - "type": "object", - "description": "A pending message was removed (steering or queued).\n\nDispatched by clients to cancel a pending message, or by the server when\nit consumes a message (e.g. starting a turn from a queued message or\ninjecting a steering message into the current turn).", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.ChatPendingMessageRemoved" - }, - "kind": { - "$ref": "#/$defs/PendingMessageKind", - "description": "Whether this is a steering or queued message" - }, - "id": { - "type": "string", - "description": "Identifier of the pending message to remove" - } - }, - "required": [ - "type", - "kind", - "id" - ] - }, - "ChatQueuedMessagesReorderedAction": { - "type": "object", - "description": "Reorder the queued messages.\n\nThe `order` array contains the IDs of queued messages in their new\ndesired order. IDs not present in the current queue are ignored.\nQueued messages whose IDs are absent from `order` are appended at\nthe end in their original relative order (so a client with a stale\nview of the queue never silently drops messages).", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.ChatQueuedMessagesReordered" - }, - "order": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Queued message IDs in the desired order" - } - }, - "required": [ - "type", - "order" - ] - }, - "ChatInputRequestedAction": { - "type": "object", - "description": "A session requested input from the user.\n\nFull-request upsert semantics: the `request` replaces any existing request\nwith the same `id`, or is appended if it is new. Answer drafts are preserved\nunless `request.answers` is provided.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.ChatInputRequested" + "$ref": "#/$defs/ActionType.SessionInputRequested" }, "request": { - "$ref": "#/$defs/ChatInputRequest", + "$ref": "#/$defs/SessionInputRequest", "description": "Input request to create or replace" } }, @@ -1243,12 +1133,12 @@ "request" ] }, - "ChatInputAnswerChangedAction": { + "SessionInputAnswerChangedAction": { "type": "object", "description": "A client updated, submitted, skipped, or removed a single in-progress answer.\n\nDispatching with `answer: undefined` removes that question's answer draft.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatInputAnswerChanged" + "$ref": "#/$defs/ActionType.SessionInputAnswerChanged" }, "requestId": { "type": "string", @@ -1259,7 +1149,7 @@ "description": "Question identifier within the input request" }, "answer": { - "$ref": "#/$defs/ChatInputAnswer", + "$ref": "#/$defs/SessionInputAnswer", "description": "Updated answer, or `undefined` to clear an answer draft" } }, @@ -1269,25 +1159,25 @@ "questionId" ] }, - "ChatInputCompletedAction": { + "SessionInputCompletedAction": { "type": "object", "description": "A client accepted, declined, or cancelled a session input request.\n\nIf accepted, the server uses `answers` (when provided) plus the request's\nsynced answer state to resume the blocked operation.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatInputCompleted" + "$ref": "#/$defs/ActionType.SessionInputCompleted" }, "requestId": { "type": "string", "description": "Input request identifier" }, "response": { - "$ref": "#/$defs/ChatInputResponseKind", + "$ref": "#/$defs/SessionInputResponseKind", "description": "Completion outcome" }, "answers": { "type": "object", "additionalProperties": { - "$ref": "#/$defs/ChatInputAnswer" + "$ref": "#/$defs/SessionInputAnswer" }, "description": "Optional final answer replacement, keyed by question ID" } @@ -1746,89 +1636,18 @@ "changes" ] }, - "ChatToolCallConfirmedAction": { + "SessionToolCallConfirmedAction": { "oneOf": [ {}, { - "$ref": "#/$defs/ChatToolCallApprovedAction" + "$ref": "#/$defs/SessionToolCallApprovedAction" }, { - "$ref": "#/$defs/ChatToolCallDeniedAction" + "$ref": "#/$defs/SessionToolCallDeniedAction" } ], "description": "Client confirms or denies a pending tool call." }, - "ChatAction": { - "oneOf": [ - {}, - { - "$ref": "#/$defs/ChatTurnStartedAction" - }, - { - "$ref": "#/$defs/ChatDeltaAction" - }, - { - "$ref": "#/$defs/ChatResponsePartAction" - }, - { - "$ref": "#/$defs/ChatToolCallStartAction" - }, - { - "$ref": "#/$defs/ChatToolCallDeltaAction" - }, - { - "$ref": "#/$defs/ChatToolCallReadyAction" - }, - { - "$ref": "#/$defs/ChatToolCallConfirmedAction" - }, - { - "$ref": "#/$defs/ChatToolCallCompleteAction" - }, - { - "$ref": "#/$defs/ChatToolCallResultConfirmedAction" - }, - { - "$ref": "#/$defs/ChatToolCallContentChangedAction" - }, - { - "$ref": "#/$defs/ChatTurnCompleteAction" - }, - { - "$ref": "#/$defs/ChatTurnCancelledAction" - }, - { - "$ref": "#/$defs/ChatErrorAction" - }, - { - "$ref": "#/$defs/ChatUsageAction" - }, - { - "$ref": "#/$defs/ChatReasoningAction" - }, - { - "$ref": "#/$defs/ChatTruncatedAction" - }, - { - "$ref": "#/$defs/ChatPendingMessageSetAction" - }, - { - "$ref": "#/$defs/ChatPendingMessageRemovedAction" - }, - { - "$ref": "#/$defs/ChatQueuedMessagesReorderedAction" - }, - { - "$ref": "#/$defs/ChatInputRequestedAction" - }, - { - "$ref": "#/$defs/ChatInputAnswerChangedAction" - }, - { - "$ref": "#/$defs/ChatInputCompletedAction" - } - ] - }, "Icon": { "type": "object", "description": "An optionally-sized icon that can be displayed in a user interface.", @@ -2219,7 +2038,7 @@ "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`)" + "description": "The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`)" }, "state": { "oneOf": [ @@ -2417,6 +2236,24 @@ "values" ] }, + "PendingMessage": { + "type": "object", + "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this pending message" + }, + "message": { + "$ref": "#/$defs/Message", + "description": "The message that will start the next turn" + } + }, + "required": [ + "id", + "message" + ] + }, "SessionState": { "type": "object", "description": "Full state for a single session, loaded when a client subscribes to the session's URI.", @@ -2444,16 +2281,34 @@ "$ref": "#/$defs/SessionActiveClient", "description": "The client currently providing tools and interactive capabilities to this session" }, - "chats": { + "turns": { + "type": "array", + "items": { + "$ref": "#/$defs/Turn" + }, + "description": "Completed turns" + }, + "activeTurn": { + "$ref": "#/$defs/ActiveTurn", + "description": "Currently in-progress turn" + }, + "steeringMessage": { + "$ref": "#/$defs/PendingMessage", + "description": "Message to inject into the current turn at a convenient point" + }, + "queuedMessages": { "type": "array", "items": { - "$ref": "#/$defs/ChatSummary" + "$ref": "#/$defs/PendingMessage" }, - "description": "Catalog of chats in this session." + "description": "Messages to send automatically as new turns after the current turn finishes" }, - "defaultChat": { - "$ref": "#/$defs/URI", - "description": "The chat that receives input when the user addresses the session without\nselecting a specific chat. This is a UI routing hint, not a hierarchy\nmarker — chats remain equal peers at the protocol level. Hosts MAY change\nthis over the session's lifetime." + "inputRequests": { + "type": "array", + "items": { + "$ref": "#/$defs/SessionInputRequest" + }, + "description": "Requests for user input that are currently blocking or informing session progress" }, "config": { "$ref": "#/$defs/SessionConfigState", @@ -2482,7 +2337,7 @@ "required": [ "summary", "lifecycle", - "chats" + "turns" ] }, "SessionActiveClient": { @@ -2537,7 +2392,6 @@ }, "SessionSummary": { "type": "object", - "description": "Lightweight catalog entry summarizing one session. Surfaced via\n{@link RootChannelCommands.listSessions | `root/listSessions`} and\n`root/sessionAdded`/`root/sessionSummaryChanged` notifications.\n\n**Aggregation across chats.** Once a session contains more than one chat,\nseveral `SessionSummary` fields are derived from the underlying\n{@link SessionState.chats | chat catalog}. Producers SHOULD follow these\nrules so clients that only consume the session summary (e.g. a session\nlist) still see meaningful state:\n\n- `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` /\n `Error` — bits 0–4) from the\n {@link SessionState.defaultChat | default chat} when present, else from\n the most recently modified chat. **Promote** `InputNeeded` whenever any\n chat in the session needs input, and **promote** `Error` whenever any\n chat is in an error state — both override the default-chat bits. The\n orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped.\n- `activity`: mirror the activity string of the default chat, or of the\n chat currently driving the promoted status bits when a non-default chat\n wins (e.g. the chat that raised `InputNeeded`).\n- `modifiedAt`: the max of all chats' `modifiedAt`.\n- `model` / `agent`: the session-level selection. Per-chat overrides are\n surfaced on individual {@link ChatSummary} entries, not aggregated up.\n- `workingDirectory`: the session-level **default**. Individual chats MAY\n override via {@link ChatSummary.workingDirectory}; aggregating these up\n is meaningless and SHOULD NOT be attempted.\n- `changes`: optional roll-up across all chats. Producers MAY sum the\n per-chat changeset stats or report the most expensive chat's stats —\n whichever is cheaper for the host to compute.\n\nSessions with a single chat trivially satisfy all of the above (the chat's\nvalues pass through unchanged). The rules only matter once a session\ncarries multiple chats.", "properties": { "resource": { "$ref": "#/$defs/URI", @@ -2581,7 +2435,7 @@ }, "workingDirectory": { "$ref": "#/$defs/URI", - "description": "The default working directory URI for this session. Individual chats\nMAY override via {@link ChatSummary.workingDirectory | their own\n`workingDirectory`}; this field acts as the fallback for any chat that\ndoes not." + "description": "The working directory URI for this session" }, "changes": { "$ref": "#/$defs/ChangesSummary", @@ -2765,1679 +2619,1571 @@ "values" ] }, - "ToolDefinition": { + "SessionInputOption": { "type": "object", - "description": "Describes a tool available in a session, provided by either the server or the active client.", + "description": "A choice in a select-style question.", "properties": { - "name": { + "id": { "type": "string", - "description": "Unique tool identifier" + "description": "Stable option identifier; for MCP enum values this is the enum string" }, - "title": { + "label": { "type": "string", - "description": "Human-readable display name" + "description": "Display label" }, "description": { "type": "string", - "description": "Description of what the tool does" + "description": "Optional secondary text" }, - "inputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." - }, - "outputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." - }, - "annotations": { - "$ref": "#/$defs/ToolAnnotations", - "description": "Behavioral hints about the tool. All properties are advisory." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." + "recommended": { + "type": "boolean", + "description": "Whether this option is the recommended/default choice" } }, "required": [ - "name" + "id", + "label" ] }, - "ToolAnnotations": { + "SessionInputQuestionBase": { "type": "object", - "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "title": { + "id": { "type": "string", - "description": "Alternate human-readable title" - }, - "readOnlyHint": { - "type": "boolean", - "description": "Tool does not modify its environment (default: false)" + "description": "Stable question identifier used as the key in `answers`" }, - "destructiveHint": { - "type": "boolean", - "description": "Tool may perform destructive updates (default: true)" + "title": { + "type": "string", + "description": "Short display title" }, - "idempotentHint": { - "type": "boolean", - "description": "Repeated calls with the same arguments have no additional effect (default: false)" + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "openWorldHint": { + "required": { "type": "boolean", - "description": "Tool may interact with external entities (default: true)" + "description": "Whether the user must answer this question to accept the request" } - } + }, + "required": [ + "id", + "message" + ] }, - "CustomizationBase": { + "SessionInputTextQuestion": { "type": "object", - "description": "Fields shared by every customization variant.", + "description": "Text question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "Stable question identifier used as the key in `answers`" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "title": { + "type": "string", + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Prompt shown to the user" }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - } - }, - "required": [ - "id", - "uri", - "name" - ] - }, - "CustomizationLoadingState": { - "type": "object", - "description": "Container is being loaded by the host.", - "properties": { "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loading" + "$ref": "#/$defs/SessionInputQuestionKind.Text" + }, + "format": { + "type": "string", + "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + }, + "min": { + "type": "number", + "description": "Minimum string length" + }, + "max": { + "type": "number", + "description": "Maximum string length" + }, + "defaultValue": { + "type": "string", + "description": "Default text" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationLoadedState": { + "SessionInputNumberQuestion": { "type": "object", - "description": "Container loaded successfully.", + "description": "Numeric question within a session input request.", "properties": { + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" + }, + "message": { + "type": "string", + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loaded" + "oneOf": [ + { + "$ref": "#/$defs/SessionInputQuestionKind.Number" + }, + { + "$ref": "#/$defs/SessionInputQuestionKind.Integer" + } + ] + }, + "min": { + "type": "number", + "description": "Minimum value" + }, + "max": { + "type": "number", + "description": "Maximum value" + }, + "defaultValue": { + "type": "number", + "description": "Default numeric value" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationDegradedState": { + "SessionInputBooleanQuestion": { "type": "object", - "description": "Container partially loaded but has warnings.", + "description": "Boolean question within a session input request.", "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Degraded" + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" }, "message": { "type": "string", - "description": "Human-readable description of the warning." + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.Boolean" + }, + "defaultValue": { + "type": "boolean", + "description": "Default boolean value" } }, "required": [ - "kind", - "message" + "id", + "message", + "kind" ] }, - "CustomizationErrorState": { + "SessionInputSingleSelectQuestion": { "type": "object", - "description": "Container failed to load.", + "description": "Single-select question within a session input request.", "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Error" + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" }, "message": { "type": "string", - "description": "Human-readable error message." + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.SingleSelect" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/$defs/SessionInputOption" + }, + "description": "Options the user may select from" + }, + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text instead of selecting an option" } }, "required": [ + "id", + "message", "kind", - "message" + "options" ] }, - "ContainerCustomizationBase": { + "SessionInputMultiSelectQuestion": { "type": "object", - "description": "Fields shared by container customizations.", + "description": "Multi-select question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "Stable question identifier used as the key in `answers`" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "title": { + "type": "string", + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Prompt shown to the user" }, - "icons": { + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.MultiSelect" + }, + "options": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "Options the user may select from" }, - "enabled": { + "allowFreeformInput": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user may enter text in addition to selecting options" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "min": { + "type": "number", + "description": "Minimum selected item count" }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" - }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "max": { + "type": "number", + "description": "Maximum selected item count" } }, "required": [ "id", - "uri", - "name", - "enabled" + "message", + "kind", + "options" ] }, - "PluginCustomization": { + "SessionInputRequest": { "type": "object", - "description": "An [Open Plugins](https://open-plugins.com/) plugin.", + "description": "A live request for user input.\n\nThe server creates or replaces requests with `session/inputRequested`.\nClients sync drafts with `session/inputAnswerChanged` and complete requests\nwith `session/inputCompleted`.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable request identifier" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Display message for the request as a whole" }, - "icons": { + "url": { + "$ref": "#/$defs/URI", + "description": "URL the user should review or open, for URL-style elicitations" + }, + "questions": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/SessionInputQuestion" }, - "description": "Icons for UI display." + "description": "Ordered questions to ask the user" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "answers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/SessionInputAnswer" + }, + "description": "Current draft or submitted answers, keyed by question ID" + } + }, + "required": [ + "id" + ] + }, + "SessionInputTextAnswerValue": { + "type": "object", + "description": "Value captured for one answer.", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Text" }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputNumberAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Number" }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "value": { + "type": "number" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputBooleanAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Boolean" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "value": { + "type": "boolean" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputSelectedAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Selected" }, - "children": { + "value": { + "type": "string" + }, + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "type": "string" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "description": "Free-form text entered instead of selecting an option" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type" + "kind", + "value" ] }, - "ClientPluginCustomization": { + "SessionInputSelectedManyAnswerValue": { "type": "object", - "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.SelectedMany" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "array", + "items": { + "type": "string" + } }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Free-form text entered in addition to selected options" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputAnswered": { + "type": "object", + "properties": { + "state": { + "oneOf": [ + { + "$ref": "#/$defs/SessionInputAnswerState.Draft" + }, + { + "$ref": "#/$defs/SessionInputAnswerState.Submitted" + } + ], + "description": "Answer state" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "value": { + "$ref": "#/$defs/SessionInputAnswerValue", + "description": "Answer value" + } + }, + "required": [ + "state", + "value" + ] + }, + "SessionInputSkipped": { + "type": "object", + "properties": { + "state": { + "$ref": "#/$defs/SessionInputAnswerState.Skipped", + "description": "Answer state" }, - "children": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "type": "string" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" - }, - "nonce": { - "type": "string", - "description": "Opaque version token used by the host to detect changes." + "description": "Free-form reason or value captured while skipping, if any" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type" + "state" ] }, - "DirectoryCustomization": { + "Turn": { "type": "object", - "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", + "description": "A completed request/response cycle.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "state": { + "$ref": "#/$defs/TurnState", + "description": "How the turn ended" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details if state is `'error'`" + } + }, + "required": [ + "id", + "message", + "responseParts", + "usage", + "state" + ] + }, + "ActiveTurn": { + "type": "object", + "description": "An in-progress turn — the assistant is actively streaming.", + "properties": { + "id": { + "type": "string", + "description": "Turn identifier" }, - "children": { + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" + }, + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/ResponsePart" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Directory" - }, - "contents": { - "$ref": "#/$defs/ChildCustomizationType", - "description": "Which child customization type this directory holds." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." }, - "writable": { - "type": "boolean", - "description": "Whether clients may write into this directory." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" } }, "required": [ "id", - "uri", - "name", - "enabled", - "type", - "contents", - "writable" + "message", + "responseParts", + "usage" ] }, - "AgentCustomization": { + "Message": { "type": "object", - "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", + "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", "properties": { - "id": { + "text": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Message text" }, - "name": { - "type": "string", - "description": "Human-readable name." + "origin": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + }, + "required": [ + "kind" + ], + "description": "The origin of the message" }, - "icons": { + "attachments": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/MessageAttachment" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Agent" - }, - "description": { - "type": "string", - "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." + "description": "File/selection attachments" }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." + "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." } }, "required": [ - "id", - "uri", - "name", - "type" + "text", + "origin" ] }, - "SkillCustomization": { + "MessageAttachmentBase": { "type": "object", - "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", + "description": "Common fields shared by all {@link MessageAttachment} variants.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." - }, - "name": { + "label": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Skill" + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "description": { + "displayKind": { "type": "string", - "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "disableModelInvocation": { - "type": "boolean", - "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." } }, "required": [ - "id", - "uri", - "name", - "type" + "label" ] }, - "PromptCustomization": { + "SimpleMessageAttachment": { "type": "object", - "description": "A prompt contributed by a plugin or directory.", + "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", "properties": { - "id": { + "label": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "name": { + "displayKind": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/CustomizationType.Prompt" + "$ref": "#/$defs/MessageAttachmentKind.Simple", + "description": "Discriminant" }, - "description": { + "modelRepresentation": { "type": "string", - "description": "Short description of what the prompt does." + "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." } }, "required": [ - "id", - "uri", - "name", + "label", "type" ] }, - "RuleCustomization": { + "MessageEmbeddedResourceAttachment": { "type": "object", - "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", + "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", "properties": { - "id": { + "label": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "name": { + "displayKind": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/CustomizationType.Rule" + "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", + "description": "Discriminant" }, - "description": { + "data": { "type": "string", - "description": "Description of what the rule enforces." + "description": "Base64-encoded binary data" }, - "alwaysApply": { - "type": "boolean", - "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." + "contentType": { + "type": "string", + "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" }, - "globs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "id", - "uri", - "name", - "type" + "label", + "type", + "data", + "contentType" ] }, - "HookCustomization": { + "MessageResourceAttachment": { "type": "object", - "description": "A hook manifest contributed by a plugin or directory.", + "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", "properties": { - "id": { + "label": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "name": { + "displayKind": { "type": "string", - "description": "Human-readable name." + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" }, "type": { - "$ref": "#/$defs/CustomizationType.Hook" + "$ref": "#/$defs/MessageAttachmentKind.Resource", + "description": "Discriminant" + }, + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "id", + "label", "uri", - "name", "type" ] }, - "McpServerCustomization": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." - }, - "name": { + "label": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "type": { - "$ref": "#/$defs/CustomizationType.McpServer" + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "enabled": { - "type": "boolean", - "description": "Whether this MCP server is currently enabled." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, - "state": { - "$ref": "#/$defs/McpServerState", - "description": "Current lifecycle state of the MCP server." + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Annotations", + "description": "Discriminant" }, - "channel": { + "resource": { "$ref": "#/$defs/URI", - "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "mcpApp": { - "$ref": "#/$defs/McpServerCustomizationApps", - "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." + "annotationIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ - "id", - "uri", - "name", + "label", "type", - "enabled", - "state" + "resource" ] }, - "McpServerCustomizationApps": { + "MarkdownResponsePart": { "type": "object", - "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", "properties": { - "capabilities": { - "$ref": "#/$defs/AhpMcpUiHostCapabilities", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." + "kind": { + "$ref": "#/$defs/ResponsePartKind.Markdown", + "description": "Discriminant" + }, + "id": { + "type": "string", + "description": "Part identifier, used by `session/delta` to target this part for content appends" + }, + "content": { + "type": "string", + "description": "Markdown content" } }, "required": [ - "capabilities" + "kind", + "id", + "content" ] }, - "AhpMcpUiHostCapabilities": { + "ResourceReponsePart": { "type": "object", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", + "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { - "serverTools": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `tools/*` methods to the upstream server." + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" }, - "serverResources": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `resources/*` methods to the upstream server." + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" }, - "logging": { - "type": "object", - "additionalProperties": {}, - "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + "contentType": { + "type": "string", + "description": "Content MIME type" }, - "sampling": { - "type": "object", - "properties": { - "tools": { - "type": "string" - } - }, - "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." - } - } - }, - "McpServerStartingState": { - "type": "object", - "description": "Server is registered with the host but has not yet started.", - "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Starting" + "$ref": "#/$defs/ResponsePartKind.ContentRef", + "description": "Discriminant" } }, "required": [ + "uri", "kind" ] }, - "McpServerReadyState": { + "ToolCallResponsePart": { "type": "object", - "description": "Server is running and serving requests.", + "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Ready" + "$ref": "#/$defs/ResponsePartKind.ToolCall", + "description": "Discriminant" + }, + "toolCall": { + "$ref": "#/$defs/ToolCallState", + "description": "Full tool call lifecycle state" } }, "required": [ - "kind" + "kind", + "toolCall" ] }, - "McpServerAuthRequiredState": { + "ReasoningResponsePart": { "type": "object", - "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", + "description": "Reasoning/thinking content from the model.", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.AuthRequired" - }, - "reason": { - "$ref": "#/$defs/McpAuthRequiredReason", - "description": "Why authentication is required." - }, - "resource": { - "$ref": "#/$defs/ProtectedResourceMetadata", - "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." + "$ref": "#/$defs/ResponsePartKind.Reasoning", + "description": "Discriminant" }, - "requiredScopes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." + "id": { + "type": "string", + "description": "Part identifier, used by `session/reasoning` to target this part for content appends" }, - "description": { + "content": { "type": "string", - "description": "Human-readable hint, typically from the OAuth `error_description`." + "description": "Accumulated reasoning text" } }, "required": [ "kind", - "reason", - "resource" + "id", + "content" ] }, - "McpServerErrorState": { + "SystemNotificationResponsePart": { "type": "object", - "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", + "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Error" + "$ref": "#/$defs/ResponsePartKind.SystemNotification", + "description": "Discriminant" }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details." + "content": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "The text of the system notification" } }, "required": [ "kind", - "error" + "content" ] }, - "McpServerStoppedState": { + "ConfirmationOption": { "type": "object", - "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", + "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.Stopped" - } - }, - "required": [ - "kind" - ] - }, - "ChatState": { - "type": "object", - "description": "Full state for a single chat, loaded when a client subscribes to the chat's\nURI.\n\nThe lightweight catalog representation of a chat is {@link ChatSummary},\ncarried in {@link SessionState.chats | `SessionState.chats`}. `ChatState`\n**denormalizes** every {@link ChatSummary} field directly onto itself so\nsubscribers receive one flat object instead of having to merge a nested\n`summary` sub-object. Producers MUST keep the two representations\nconsistent: any change to the inlined fields below SHOULD also be\nannounced on the parent session via the matching\n{@link SessionChatUpdatedAction | `session/chatUpdated`} action.", - "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { + "id": { "type": "string", - "description": "Human-readable description of what the chat is currently doing" + "description": "Unique identifier for the option, returned in the confirmed action" }, - "modifiedAt": { + "label": { "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nHosts MAY override this for individual chats — for example, to give a\nsubordinate chat its own git worktree so multiple chats in a session can\nmake independent edits that the orchestrator later merges back." - }, - "turns": { - "type": "array", - "items": { - "$ref": "#/$defs/Turn" - }, - "description": "Completed turns" - }, - "activeTurn": { - "$ref": "#/$defs/ActiveTurn", - "description": "Currently in-progress turn" - }, - "steeringMessage": { - "$ref": "#/$defs/PendingMessage", - "description": "Message to inject into the current turn at a convenient point" - }, - "queuedMessages": { - "type": "array", - "items": { - "$ref": "#/$defs/PendingMessage" - }, - "description": "Messages to send automatically as new turns after the current turn finishes" + "description": "Human-readable label displayed to the user" }, - "inputRequests": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputRequest" - }, - "description": "Requests for user input that are currently blocking or informing chat progress" + "kind": { + "$ref": "#/$defs/ConfirmationOptionKind", + "description": "Whether this option represents an approval or denial" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this chat." + "group": { + "type": "number", + "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." } }, "required": [ - "resource", - "title", - "status", - "modifiedAt", - "turns" + "id", + "label", + "kind" ] }, - "ChatSummary": { + "ToolCallClientContributor": { "type": "object", - "description": "Lightweight catalog entry for a chat, carried in\n{@link SessionState.chats | `SessionState.chats`}. The full conversation\nlives in {@link ChatState}, which inlines (denormalizes) every field below.", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { - "type": "string", - "description": "Human-readable description of what the chat is currently doing" + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.Client" }, - "modifiedAt": { + "clientId": { "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nSee {@link ChatState.workingDirectory} for usage notes." + "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `session/toolCallComplete` with the result." } }, "required": [ - "resource", - "title", - "status", - "modifiedAt" + "kind", + "clientId" ] }, - "PendingMessage": { + "ToolCallMcpContributor": { "type": "object", - "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", "properties": { - "id": { - "type": "string", - "description": "Unique identifier for this pending message" + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.MCP" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that will start the next turn" + "customizationId": { + "type": "string", + "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." } }, "required": [ - "id", - "message" + "kind", + "customizationId" ] }, - "ChatInputOption": { + "ToolCallBase": { "type": "object", - "description": "A choice in a select-style question.", + "description": "Metadata common to all tool call states.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable option identifier; for MCP enum values this is the enum string" + "description": "Unique tool call identifier" }, - "label": { + "toolName": { "type": "string", - "description": "Display label" + "description": "Internal tool name (for debugging/logging)" }, - "description": { + "displayName": { "type": "string", - "description": "Optional secondary text" + "description": "Human-readable tool name" }, - "recommended": { - "type": "boolean", - "description": "Whether this option is the recommended/default choice" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." } }, "required": [ - "id", - "label" + "toolCallId", + "toolName", + "displayName" ] }, - "ChatInputQuestionBase": { + "ToolCallParameterFields": { "type": "object", + "description": "Properties available once tool call parameters are fully received.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "message": { + "toolInput": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "description": "Raw tool input" } }, "required": [ - "id", - "message" + "invocationMessage" ] }, - "ChatInputTextQuestion": { + "ToolCallResult": { "type": "object", - "description": "Text question within a chat input request.", + "description": "Tool execution result details, available after execution completes.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" - }, - "message": { - "type": "string", - "description": "Prompt shown to the user" - }, - "required": { + "success": { "type": "boolean", - "description": "Whether the user must answer this question to accept the request" - }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Text" + "description": "Whether the tool succeeded" }, - "format": { - "type": "string", - "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" }, - "min": { - "type": "number", - "description": "Minimum string length" + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." }, - "max": { - "type": "number", - "description": "Maximum string length" + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." }, - "defaultValue": { - "type": "string", - "description": "Default text" + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" } }, "required": [ - "id", - "message", - "kind" + "success", + "pastTenseMessage" ] }, - "ChatInputNumberQuestion": { + "ToolCallStreamingState": { "type": "object", - "description": "Numeric question within a chat input request.", + "description": "LM is streaming the tool call parameters.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputQuestionKind.Number" - }, - { - "$ref": "#/$defs/ChatInputQuestionKind.Integer" - } - ] + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "min": { - "type": "number", - "description": "Minimum value" + "status": { + "$ref": "#/$defs/ToolCallStatus.Streaming" }, - "max": { - "type": "number", - "description": "Maximum value" + "partialInput": { + "type": "string", + "description": "Partial parameters accumulated so far" }, - "defaultValue": { - "type": "number", - "description": "Default numeric value" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Progress message shown while parameters are streaming" } }, "required": [ - "id", - "message", - "kind" + "toolCallId", + "toolName", + "displayName", + "status" ] }, - "ChatInputBooleanQuestion": { + "ToolCallPendingConfirmationState": { "type": "object", - "description": "Boolean question within a chat input request.", + "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Boolean" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "defaultValue": { - "type": "boolean", - "description": "Default boolean value" - } - }, - "required": [ - "id", - "message", - "kind" - ] - }, - "ChatInputSingleSelectQuestion": { - "type": "object", - "description": "Single-select question within a chat input request.", - "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "title": { + "toolInput": { "type": "string", - "description": "Short display title" + "description": "Raw tool input" }, - "message": { - "type": "string", - "description": "Prompt shown to the user" + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "confirmationTitle": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.SingleSelect" + "edits": { + "type": "object", + "properties": { + "items": { + "type": "string" + } + }, + "required": [ + "items" + ], + "description": "File edits that this tool call will perform, for preview before confirmation" + }, + "editable": { + "type": "boolean", + "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" }, "options": { "type": "array", "items": { - "$ref": "#/$defs/ChatInputOption" + "$ref": "#/$defs/ConfirmationOption" }, - "description": "Options the user may select from" - }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text instead of selecting an option" + "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." } }, "required": [ - "id", - "message", - "kind", - "options" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status" ] }, - "ChatInputMultiSelectQuestion": { + "ToolCallRunningState": { "type": "object", - "description": "Multi-select question within a chat input request.", + "description": "Tool is actively executing.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.MultiSelect" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "options": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputOption" - }, - "description": "Options the user may select from" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text in addition to selecting options" + "toolInput": { + "type": "string", + "description": "Raw tool input" }, - "min": { - "type": "number", - "description": "Minimum selected item count" + "status": { + "$ref": "#/$defs/ToolCallStatus.Running" }, - "max": { - "type": "number", - "description": "Maximum selected item count" + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." } }, "required": [ - "id", - "message", - "kind", - "options" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "confirmed" ] }, - "ChatInputRequest": { + "ToolCallPendingResultConfirmationState": { "type": "object", - "description": "A live request for user input.\n\nThe server creates or replaces requests with `chat/inputRequested`.\nClients sync drafts with `chat/inputAnswerChanged` and complete requests\nwith `chat/inputCompleted`.", + "description": "Tool finished executing, waiting for client to approve the result.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable request identifier" + "description": "Unique tool call identifier" }, - "message": { + "toolName": { "type": "string", - "description": "Display message for the request as a whole" + "description": "Internal tool name (for debugging/logging)" }, - "url": { - "$ref": "#/$defs/URI", - "description": "URL the user should review or open, for URL-style elicitations" + "displayName": { + "type": "string", + "description": "Human-readable tool name" }, - "questions": { + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" + }, + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" + }, + "content": { "type": "array", "items": { - "$ref": "#/$defs/ChatInputQuestion" + "$ref": "#/$defs/ToolResultContent" }, - "description": "Ordered questions to ask the user" + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." }, - "answers": { + "structuredContent": { "type": "object", - "additionalProperties": { - "$ref": "#/$defs/ChatInputAnswer" - }, - "description": "Current draft or submitted answers, keyed by question ID" - } - }, - "required": [ - "id" - ] - }, - "ChatInputTextAnswerValue": { - "type": "object", - "description": "Value captured for one answer.", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Text" + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." }, - "value": { - "type": "string" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputNumberAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Number" + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" }, - "value": { - "type": "number" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputBooleanAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Boolean" + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" }, - "value": { - "type": "boolean" + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "kind", - "value" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "ChatInputSelectedAnswerValue": { + "ToolCallCompletedState": { "type": "object", + "description": "Tool completed successfully or with an error.", "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Selected" + "toolCallId": { + "type": "string", + "description": "Unique tool call identifier" }, - "value": { - "type": "string" + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form text entered instead of selecting an option" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedManyAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.SelectedMany" + "displayName": { + "type": "string", + "description": "Human-readable tool name" }, - "value": { - "type": "array", - "items": { - "type": "string" - } + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "freeformValues": { + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" + }, + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" + }, + "content": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/ToolResultContent" }, - "description": "Free-form text entered in addition to selected options" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputAnswered": { - "type": "object", - "properties": { - "state": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputAnswerState.Draft" + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + }, + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + }, + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" }, - { - "$ref": "#/$defs/ChatInputAnswerState.Submitted" + "code": { + "type": "string" } + }, + "required": [ + "message" ], - "description": "Answer state" + "description": "Error details if the tool failed" }, - "value": { - "$ref": "#/$defs/ChatInputAnswerValue", - "description": "Answer value" - } - }, - "required": [ - "state", - "value" - ] - }, - "ChatInputSkipped": { - "type": "object", - "properties": { - "state": { - "$ref": "#/$defs/ChatInputAnswerState.Skipped", - "description": "Answer state" + "status": { + "$ref": "#/$defs/ToolCallStatus.Completed" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form reason or value captured while skipping, if any" + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "state" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "Turn": { + "ToolCallCancelledState": { "type": "object", - "description": "A completed request/response cycle.", + "description": "Tool call was cancelled before execution.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Turn identifier" + "description": "Unique tool call identifier" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "responseParts": { - "type": "array", - "items": { - "$ref": "#/$defs/ResponsePart" - }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." + "displayName": { + "type": "string", + "description": "Human-readable tool name" }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "state": { - "$ref": "#/$defs/TurnState", - "description": "How the turn ended" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details if state is `'error'`" - } - }, - "required": [ - "id", - "message", - "responseParts", - "usage", - "state" - ] - }, - "ActiveTurn": { - "type": "object", - "description": "An in-progress turn — the assistant is actively streaming.", - "properties": { - "id": { + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { "type": "string", - "description": "Turn identifier" + "description": "Raw tool input" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "status": { + "$ref": "#/$defs/ToolCallStatus.Cancelled" }, - "responseParts": { - "type": "array", - "items": { - "$ref": "#/$defs/ResponsePart" - }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." + "reason": { + "$ref": "#/$defs/ToolCallCancellationReason", + "description": "Why the tool was cancelled" }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" + "reasonMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Optional message explaining the cancellation" + }, + "userSuggestion": { + "$ref": "#/$defs/Message", + "description": "What the user suggested doing instead" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "id", - "message", - "responseParts", - "usage" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "reason" ] }, - "Message": { + "ToolDefinition": { "type": "object", - "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", + "description": "Describes a tool available in a session, provided by either the server or the active client.", "properties": { - "text": { + "name": { "type": "string", - "description": "Message text" + "description": "Unique tool identifier" }, - "origin": { + "title": { + "type": "string", + "description": "Human-readable display name" + }, + "description": { + "type": "string", + "description": "Description of what the tool does" + }, + "inputSchema": { "type": "object", "properties": { - "kind": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { "type": "string" } }, "required": [ - "kind" + "type" ], - "description": "The origin of the message" + "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." }, - "attachments": { - "type": "array", - "items": { - "$ref": "#/$defs/MessageAttachment" + "outputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } }, - "description": "File/selection attachments" + "required": [ + "type" + ], + "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." + }, + "annotations": { + "$ref": "#/$defs/ToolAnnotations", + "description": "Behavioral hints about the tool. All properties are advisory." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." + "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." } }, "required": [ - "text", - "origin" + "name" ] }, - "MessageAttachmentBase": { + "ToolAnnotations": { "type": "object", - "description": "Common fields shared by all {@link MessageAttachment} variants.", + "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "label": { + "title": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Alternate human-readable title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "readOnlyHint": { + "type": "boolean", + "description": "Tool does not modify its environment (default: false)" }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "destructiveHint": { + "type": "boolean", + "description": "Tool may perform destructive updates (default: true)" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "idempotentHint": { + "type": "boolean", + "description": "Repeated calls with the same arguments have no additional effect (default: false)" + }, + "openWorldHint": { + "type": "boolean", + "description": "Tool may interact with external entities (default: true)" } - }, - "required": [ - "label" - ] + } }, - "SimpleMessageAttachment": { + "ToolResultTextContent": { "type": "object", - "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", + "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." - }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Simple", - "description": "Discriminant" + "$ref": "#/$defs/ToolResultContentType.Text" }, - "modelRepresentation": { + "text": { "type": "string", - "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." + "description": "The text content" } }, "required": [ - "label", - "type" + "type", + "text" ] }, - "MessageEmbeddedResourceAttachment": { + "ToolResultEmbeddedResourceContent": { "type": "object", - "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", + "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." - }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", - "description": "Discriminant" + "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" }, "data": { "type": "string", - "description": "Base64-encoded binary data" + "description": "Base64-encoded data" }, "contentType": { "type": "string", - "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" - }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." + "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" } }, "required": [ - "label", "type", "data", "contentType" ] }, - "MessageResourceAttachment": { + "ToolResultResourceContent": { "type": "object", - "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", + "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." - }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, "uri": { "$ref": "#/$defs/URI", "description": "Content URI" @@ -4451,914 +4197,873 @@ "description": "Content MIME type" }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Resource", - "description": "Discriminant" - }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." + "$ref": "#/$defs/ToolResultContentType.Resource" } }, "required": [ - "label", "uri", "type" ] }, - "MessageAnnotationsAttachment": { + "ToolResultFileEditContent": { "type": "object", - "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", + "description": "Describes a file modification performed by a tool.", "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." - }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." - }, - "_meta": { + "before": { "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Annotations", - "description": "Discriminant" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." - }, - "annotationIds": { - "type": "array", - "items": { - "type": "string" + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } }, - "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." - } - }, - "required": [ - "label", - "type", - "resource" - ] - }, - "MarkdownResponsePart": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Markdown", - "description": "Discriminant" - }, - "id": { - "type": "string", - "description": "Part identifier, used by `chat/delta` to target this part for content appends" - }, - "content": { - "type": "string", - "description": "Markdown content" - } - }, - "required": [ - "kind", - "id", - "content" - ] - }, - "ResourceReponsePart": { - "type": "object", - "description": "A content part that's a reference to large content stored outside the state tree.", - "properties": { - "uri": { - "$ref": "#/$defs/URI", - "description": "Content URI" + "required": [ + "uri", + "content" + ], + "description": "The file state before the edit. Absent for file creations or for in-place file edits." }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" + "after": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state after the edit. Absent for file deletions." }, - "contentType": { - "type": "string", - "description": "Content MIME type" + "diff": { + "type": "object", + "properties": { + "added": { + "type": "number" + }, + "removed": { + "type": "number" + } + }, + "description": "Optional diff display metadata" }, - "kind": { - "$ref": "#/$defs/ResponsePartKind.ContentRef", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.FileEdit" } }, "required": [ - "uri", - "kind" + "type" ] }, - "ToolCallResponsePart": { + "ToolResultTerminalContent": { "type": "object", - "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.ToolCall", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Terminal" }, - "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "Full tool call lifecycle state" + "resource": { + "$ref": "#/$defs/URI", + "description": "Terminal URI (subscribable for full terminal state)" + }, + "title": { + "type": "string", + "description": "Display title for the terminal content" } }, "required": [ - "kind", - "toolCall" + "type", + "resource", + "title" ] }, - "ReasoningResponsePart": { + "ToolResultSubagentContent": { "type": "object", - "description": "Reasoning/thinking content from the model.", + "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Reasoning", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Subagent" }, - "id": { + "resource": { + "$ref": "#/$defs/URI", + "description": "Subagent session URI (subscribable for full session state)" + }, + "title": { "type": "string", - "description": "Part identifier, used by `chat/reasoning` to target this part for content appends" + "description": "Display title for the subagent" }, - "content": { + "agentName": { "type": "string", - "description": "Accumulated reasoning text" - } - }, - "required": [ - "kind", - "id", - "content" - ] - }, - "SystemNotificationResponsePart": { - "type": "object", - "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", - "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.SystemNotification", - "description": "Discriminant" + "description": "Internal agent name" }, - "content": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "The text of the system notification" + "description": { + "type": "string", + "description": "Human-readable description of the subagent's task" } }, "required": [ - "kind", - "content" + "type", + "resource", + "title" ] }, - "ConfirmationOption": { + "CustomizationBase": { "type": "object", - "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", + "description": "Fields shared by every customization variant.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the option, returned in the confirmed action" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "label": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Human-readable label displayed to the user" + "description": "Human-readable name." }, - "kind": { - "$ref": "#/$defs/ConfirmationOptionKind", - "description": "Whether this option represents an approval or denial" + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "group": { - "type": "number", - "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." } }, "required": [ "id", - "label", - "kind" + "uri", + "name" ] }, - "ToolCallClientContributor": { + "CustomizationLoadingState": { "type": "object", + "description": "Container is being loaded by the host.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.Client" - }, - "clientId": { - "type": "string", - "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `chat/toolCallComplete` with the result." + "$ref": "#/$defs/CustomizationLoadStatus.Loading" } }, "required": [ - "kind", - "clientId" + "kind" ] }, - "ToolCallMcpContributor": { + "CustomizationLoadedState": { "type": "object", + "description": "Container loaded successfully.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.MCP" - }, - "customizationId": { - "type": "string", - "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." + "$ref": "#/$defs/CustomizationLoadStatus.Loaded" } }, "required": [ - "kind", - "customizationId" + "kind" ] }, - "ToolCallBase": { + "CustomizationDegradedState": { "type": "object", - "description": "Metadata common to all tool call states.", + "description": "Container partially loaded but has warnings.", "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Degraded" }, - "displayName": { + "message": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "description": "Human-readable description of the warning." } }, "required": [ - "toolCallId", - "toolName", - "displayName" + "kind", + "message" ] }, - "ToolCallParameterFields": { + "CustomizationErrorState": { "type": "object", - "description": "Properties available once tool call parameters are fully received.", + "description": "Container failed to load.", "properties": { - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Error" }, - "toolInput": { + "message": { "type": "string", - "description": "Raw tool input" + "description": "Human-readable error message." } }, "required": [ - "invocationMessage" + "kind", + "message" ] }, - "ToolCallResult": { + "ContainerCustomizationBase": { "type": "object", - "description": "Tool execution result details, available after execution completes.", + "description": "Fields shared by container customizations.", "properties": { - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "content": { + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." + }, + "clientId": { + "type": "string", + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + }, + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + }, + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/ChildCustomization" }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." } }, "required": [ - "success", - "pastTenseMessage" + "id", + "uri", + "name", + "enabled" ] }, - "ToolCallStreamingState": { + "PluginCustomization": { "type": "object", - "description": "LM is streaming the tool call parameters.", + "description": "An [Open Plugins](https://open-plugins.com/) plugin.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Streaming" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "partialInput": { + "clientId": { "type": "string", - "description": "Partial parameters accumulated so far" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Progress message shown while parameters are streaming" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + }, + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/ChildCustomization" + }, + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "status" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallPendingConfirmationState": { + "ClientPluginCustomization": { "type": "object", - "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", + "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" - }, - "confirmationTitle": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" - }, - "edits": { - "type": "object", - "properties": { - "items": { - "type": "string" - } - }, - "required": [ - "items" - ], - "description": "File edits that this tool call will perform, for preview before confirmation" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "editable": { - "type": "boolean", - "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "options": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ConfirmationOption" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" + }, + "nonce": { + "type": "string", + "description": "Opaque version token used by the host to detect changes." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallRunningState": { + "DirectoryCustomization": { "type": "object", - "description": "Tool is actively executing.", + "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Running" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "content": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Directory" + }, + "contents": { + "$ref": "#/$defs/ChildCustomizationType", + "description": "Which child customization type this directory holds." + }, + "writable": { + "type": "boolean", + "description": "Whether clients may write into this directory." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "enabled", + "type", + "contents", + "writable" ] }, - "ToolCallPendingResultConfirmationState": { + "AgentCustomization": { "type": "object", - "description": "Tool finished executing, waiting for client to approve the result.", + "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" - }, - "displayName": { + "id": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "toolInput": { + "name": { "type": "string", - "description": "Raw tool input" - }, - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" - }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "description": "Human-readable name." }, - "content": { + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } - }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" + "type": { + "$ref": "#/$defs/CustomizationType.Agent" }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": { + "type": "string", + "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "type" ] }, - "ToolCallCompletedState": { + "SkillCustomization": { "type": "object", - "description": "Tool completed successfully or with an error.", + "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "type": { + "$ref": "#/$defs/CustomizationType.Skill" }, - "toolInput": { + "description": { "type": "string", - "description": "Raw tool input" + "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." }, - "success": { + "disableModelInvocation": { "type": "boolean", - "description": "Whether the tool succeeded" + "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." + } + }, + "required": [ + "id", + "uri", + "name", + "type" + ] + }, + "PromptCustomization": { + "type": "object", + "description": "A prompt contributed by a plugin or directory.", + "properties": { + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "content": { + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" - }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." - }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } + "$ref": "#/$defs/Icon" }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "description": "Icons for UI display." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Completed" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "type": { + "$ref": "#/$defs/CustomizationType.Prompt" }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "description": { + "type": "string", + "description": "Short description of what the prompt does." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "type" ] }, - "ToolCallCancelledState": { + "RuleCustomization": { "type": "object", - "description": "Tool call was cancelled before execution.", + "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "description": "Human-readable name." }, - "toolInput": { - "type": "string", - "description": "Raw tool input" + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Cancelled" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "reason": { - "$ref": "#/$defs/ToolCallCancellationReason", - "description": "Why the tool was cancelled" + "type": { + "$ref": "#/$defs/CustomizationType.Rule" }, - "reasonMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Optional message explaining the cancellation" + "description": { + "type": "string", + "description": "Description of what the rule enforces." }, - "userSuggestion": { - "$ref": "#/$defs/Message", - "description": "What the user suggested doing instead" + "alwaysApply": { + "type": "boolean", + "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "globs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "reason" + "id", + "uri", + "name", + "type" ] }, - "ToolResultTextContent": { + "HookCustomization": { "type": "object", - "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", + "description": "A hook manifest contributed by a plugin or directory.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Text" + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "text": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "The text content" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Hook" } }, "required": [ - "type", - "text" + "id", + "uri", + "name", + "type" ] }, - "ToolResultEmbeddedResourceContent": { + "McpServerCustomization": { "type": "object", - "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", + "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" - }, - "data": { + "id": { "type": "string", - "description": "Base64-encoded data" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "contentType": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.McpServer" + }, + "enabled": { + "type": "boolean", + "description": "Whether this MCP server is currently enabled." + }, + "state": { + "$ref": "#/$defs/McpServerState", + "description": "Current lifecycle state of the MCP server." + }, + "channel": { + "$ref": "#/$defs/URI", + "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + }, + "mcpApp": { + "$ref": "#/$defs/McpServerCustomizationApps", + "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." } }, "required": [ + "id", + "uri", + "name", "type", - "data", - "contentType" + "enabled", + "state" ] }, - "ToolResultResourceContent": { + "McpServerCustomizationApps": { "type": "object", - "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", + "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", "properties": { - "uri": { - "$ref": "#/$defs/URI", - "description": "Content URI" - }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" - }, - "contentType": { - "type": "string", - "description": "Content MIME type" - }, - "type": { - "$ref": "#/$defs/ToolResultContentType.Resource" + "capabilities": { + "$ref": "#/$defs/AhpMcpUiHostCapabilities", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." } }, "required": [ - "uri", - "type" + "capabilities" ] }, - "ToolResultFileEditContent": { + "AhpMcpUiHostCapabilities": { "type": "object", - "description": "Describes a file modification performed by a tool.", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", "properties": { - "before": { + "serverTools": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state before the edit. Absent for file creations or for in-place file edits." + "description": "Producer proxies the MCP `tools/*` methods to the upstream server." }, - "after": { + "serverResources": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state after the edit. Absent for file deletions." + "description": "Producer proxies the MCP `resources/*` methods to the upstream server." }, - "diff": { + "logging": { + "type": "object", + "additionalProperties": {}, + "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + }, + "sampling": { "type": "object", "properties": { - "added": { - "type": "number" - }, - "removed": { - "type": "number" + "tools": { + "type": "string" } }, - "description": "Optional diff display metadata" + "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." + } + } + }, + "McpServerStartingState": { + "type": "object", + "description": "Server is registered with the host but has not yet started.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Starting" + } + }, + "required": [ + "kind" + ] + }, + "McpServerReadyState": { + "type": "object", + "description": "Server is running and serving requests.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Ready" + } + }, + "required": [ + "kind" + ] + }, + "McpServerAuthRequiredState": { + "type": "object", + "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.AuthRequired" }, - "type": { - "$ref": "#/$defs/ToolResultContentType.FileEdit" + "reason": { + "$ref": "#/$defs/McpAuthRequiredReason", + "description": "Why authentication is required." + }, + "resource": { + "$ref": "#/$defs/ProtectedResourceMetadata", + "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." + }, + "requiredScopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." + }, + "description": { + "type": "string", + "description": "Human-readable hint, typically from the OAuth `error_description`." } }, "required": [ - "type" + "kind", + "reason", + "resource" ] }, - "ToolResultTerminalContent": { + "McpServerErrorState": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Terminal" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Terminal URI (subscribable for full terminal state)" + "kind": { + "$ref": "#/$defs/McpServerStatus.Error" }, - "title": { - "type": "string", - "description": "Display title for the terminal content" + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details." } }, "required": [ - "type", - "resource", - "title" + "kind", + "error" ] }, - "ToolResultSubagentContent": { + "McpServerStoppedState": { "type": "object", - "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", + "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Subagent" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Subagent session URI (subscribable for full session state)" - }, - "title": { - "type": "string", - "description": "Display title for the subagent" - }, - "agentName": { - "type": "string", - "description": "Internal agent name" - }, - "description": { - "type": "string", - "description": "Human-readable description of the subagent's task" + "kind": { + "$ref": "#/$defs/McpServerStatus.Stopped" } }, "required": [ - "type", - "resource", - "title" + "kind" ] }, "TerminalInfo": { @@ -5767,320 +5472,164 @@ "type": "string", "description": "Stable identifier within the enclosing annotation. Assigned by the client\nthat dispatches the {@link AnnotationsEntrySetAction} (or the enclosing\n{@link AnnotationsSetAction}) introducing the entry." }, - "text": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Entry body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." - } - }, - "required": [ - "id", - "text" - ] - }, - "TelemetryCapabilities": { - "type": "object", - "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", - "properties": { - "logs": { - "$ref": "#/$defs/URI", - "description": "Channel URI (or RFC 6570 URI template) for OTLP log records\n(`otlp/exportLogs` notifications).\n\nThe following template variables are defined by this protocol; any\nother variable name MUST be ignored by clients (there is no\nprotocol-defined way to obtain values for unknown variables):\n\n| Variables in template | Meaning |\n| --------------------- | ------------------------------------------------------------------------------------------------------- |\n| _(none)_ | The host does not support subscriber-side severity filtering. The template is itself a subscribable URI. |\n| `{level}` | Minimum OTLP severity to deliver. Expand to one of the [OTLP `SeverityNumber`](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber) short names (case-insensitive): `trace`, `debug`, `info`, `warn`, `error`, `fatal`. The server delivers log records whose `severityNumber` falls in the corresponding band or above. |\n\nHosts SHOULD honour the expanded `{level}`; clients MUST still filter\ndefensively in case a host ignores the parameter. Hosts that do not\nadvertise `{level}` deliver all severities.\n\nFuture protocol versions MAY add new well-known variables (e.g. scope\nor attribute filters)." - }, - "traces": { - "$ref": "#/$defs/URI", - "description": "Channel URI for OTLP spans (`otlp/exportTraces` notifications). No\ntemplate variables are defined by this protocol version." - }, - "metrics": { - "$ref": "#/$defs/URI", - "description": "Channel URI for OTLP metric data points (`otlp/exportMetrics`\nnotifications). No template variables are defined by this protocol\nversion." - } - } - }, - "ResourceWatchState": { - "type": "object", - "description": "Full state for a single resource watch, returned when a client subscribes\nto an `ahp-resource-watch:` URI.\n\nWatches are otherwise stateless: the watcher exists to deliver\n{@link ResourceWatchChangedAction} events. The state carries only the\ndescriptor of what is being watched so a re-subscribing client can\nrecover the watch configuration after reconnecting.", - "properties": { - "root": { - "$ref": "#/$defs/URI", - "description": "The URI being watched. For recursive watches this is the root of the\nsubtree; for non-recursive watches this is the single file or\ndirectory." - }, - "recursive": { - "type": "boolean", - "description": "`true` if the watcher reports changes for descendants of `root`;\n`false` if it only reports changes to `root` itself (and, when\n`root` is a directory, its direct children)." - }, - "excludes": { - "type": "object", - "properties": { - "items": { - "type": "string" - } - }, - "required": [ - "items" - ], - "description": "Optional glob patterns or paths relative to `root` to exclude from\nchange reporting." - }, - "includes": { - "type": "object", - "properties": { - "items": { - "type": "string" - } - }, - "required": [ - "items" - ], - "description": "Optional glob patterns or paths relative to `root` to restrict\nchange reporting to. Omit to report every change under `root`\nsubject to `excludes`." - } - }, - "required": [ - "root", - "recursive" - ] - }, - "ResourceChange": { - "type": "object", - "description": "A single change observed by a resource watcher.", - "properties": { - "uri": { - "$ref": "#/$defs/URI", - "description": "The URI of the resource that changed." - }, - "type": { - "$ref": "#/$defs/ResourceChangeType", - "description": "The kind of change observed." - } - }, - "required": [ - "uri", - "type" - ] - }, - "StringOrMarkdown": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "markdown": { - "type": "string" - } - }, - "required": [ - "markdown" - ] - } - ], - "description": "A string that may optionally be rendered as Markdown.\n\n- A plain `string` is rendered as-is (no Markdown processing).\n- An object with `{ markdown: string }` is rendered with Markdown formatting." - }, - "ChildCustomizationType": { - "oneOf": [ - {}, - { - "$ref": "#/$defs/CustomizationType.Agent" - }, - { - "$ref": "#/$defs/CustomizationType.Skill" - }, - { - "$ref": "#/$defs/CustomizationType.Prompt" - }, - { - "$ref": "#/$defs/CustomizationType.Rule" - }, - { - "$ref": "#/$defs/CustomizationType.Hook" - }, - { - "$ref": "#/$defs/CustomizationType.McpServer" - } - ], - "description": "Customization types that appear as children of a\n{@link PluginCustomization} or {@link DirectoryCustomization}." - }, - "CustomizationLoadState": { - "oneOf": [ - {}, - { - "$ref": "#/$defs/CustomizationLoadingState" - }, - { - "$ref": "#/$defs/CustomizationLoadedState" - }, - { - "$ref": "#/$defs/CustomizationDegradedState" - }, - { - "$ref": "#/$defs/CustomizationErrorState" - } - ], - "description": "Discriminated load state for a container customization\n({@link PluginCustomization} or {@link DirectoryCustomization})." - }, - "ChildCustomization": { - "oneOf": [ - {}, - { - "$ref": "#/$defs/AgentCustomization" - }, - { - "$ref": "#/$defs/SkillCustomization" - }, - { - "$ref": "#/$defs/PromptCustomization" - }, - { - "$ref": "#/$defs/RuleCustomization" - }, - { - "$ref": "#/$defs/HookCustomization" - }, - { - "$ref": "#/$defs/McpServerCustomization" - } - ], - "description": "Child customizations that live inside a {@link PluginCustomization} or\n{@link DirectoryCustomization}." - }, - "Customization": { - "oneOf": [ - {}, - { - "$ref": "#/$defs/PluginCustomization" - }, - { - "$ref": "#/$defs/DirectoryCustomization" - }, - { - "$ref": "#/$defs/McpServerCustomization" - } - ], - "description": "A top-level customization active in a session. Either a container\n({@link PluginCustomization} or {@link DirectoryCustomization}) whose\nleaf customizations live in its\n{@link ContainerCustomizationBase.children | `children`} array, or a\nbare {@link McpServerCustomization} surfaced directly by the host." - }, - "McpServerState": { - "oneOf": [ - {}, - { - "$ref": "#/$defs/McpServerStartingState" - }, - { - "$ref": "#/$defs/McpServerReadyState" + "text": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Entry body. A bare `string` is rendered as plain text; pass\n`{ markdown: \"…\" }` to opt into Markdown rendering. See\n{@link StringOrMarkdown}." }, - { - "$ref": "#/$defs/McpServerAuthRequiredState" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Producer-defined opaque metadata, surfaced to tooling but not\ninterpreted by the protocol." + } + }, + "required": [ + "id", + "text" + ] + }, + "TelemetryCapabilities": { + "type": "object", + "description": "OTLP telemetry channels the agent host emits.\n\nEach field, when present, is either a literal channel URI or an\n[RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570) URI template\na client expands and then subscribes to. Absent fields indicate the host\ndoes not emit that signal.\n\nChannel URIs use the `ahp-otlp:` scheme. The scheme identifies the\nprotocol (OpenTelemetry over AHP) so clients can recognise the channel\ntype by URI alone; the host is free to choose any authority/path that\nmakes sense for its implementation. Clients MUST treat the URI as\nopaque (apart from expanding any well-known template variables defined\nbelow) and subscribe with the resulting concrete URI.\n\nPayloads delivered on these channels are OTLP/JSON values — see\n[opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto)\nfor the wire shapes (`ExportLogsServiceRequest`,\n`ExportTraceServiceRequest`, `ExportMetricsServiceRequest`).", + "properties": { + "logs": { + "$ref": "#/$defs/URI", + "description": "Channel URI (or RFC 6570 URI template) for OTLP log records\n(`otlp/exportLogs` notifications).\n\nThe following template variables are defined by this protocol; any\nother variable name MUST be ignored by clients (there is no\nprotocol-defined way to obtain values for unknown variables):\n\n| Variables in template | Meaning |\n| --------------------- | ------------------------------------------------------------------------------------------------------- |\n| _(none)_ | The host does not support subscriber-side severity filtering. The template is itself a subscribable URI. |\n| `{level}` | Minimum OTLP severity to deliver. Expand to one of the [OTLP `SeverityNumber`](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber) short names (case-insensitive): `trace`, `debug`, `info`, `warn`, `error`, `fatal`. The server delivers log records whose `severityNumber` falls in the corresponding band or above. |\n\nHosts SHOULD honour the expanded `{level}`; clients MUST still filter\ndefensively in case a host ignores the parameter. Hosts that do not\nadvertise `{level}` deliver all severities.\n\nFuture protocol versions MAY add new well-known variables (e.g. scope\nor attribute filters)." }, - { - "$ref": "#/$defs/McpServerErrorState" + "traces": { + "$ref": "#/$defs/URI", + "description": "Channel URI for OTLP spans (`otlp/exportTraces` notifications). No\ntemplate variables are defined by this protocol version." }, - { - "$ref": "#/$defs/McpServerStoppedState" + "metrics": { + "$ref": "#/$defs/URI", + "description": "Channel URI for OTLP metric data points (`otlp/exportMetrics`\nnotifications). No template variables are defined by this protocol\nversion." } - ], - "description": "Discriminated union of all MCP server lifecycle states.\nDiscriminated by `kind` (a {@link McpServerStatus} value)." + } }, - "ChatOrigin": { - "oneOf": [ - {}, - { + "ResourceWatchState": { + "type": "object", + "description": "Full state for a single resource watch, returned when a client subscribes\nto an `ahp-resource-watch:` URI.\n\nWatches are otherwise stateless: the watcher exists to deliver\n{@link ResourceWatchChangedAction} events. The state carries only the\ndescriptor of what is being watched so a re-subscribing client can\nrecover the watch configuration after reconnecting.", + "properties": { + "root": { + "$ref": "#/$defs/URI", + "description": "The URI being watched. For recursive watches this is the root of the\nsubtree; for non-recursive watches this is the single file or\ndirectory." + }, + "recursive": { + "type": "boolean", + "description": "`true` if the watcher reports changes for descendants of `root`;\n`false` if it only reports changes to `root` itself (and, when\n`root` is a directory, its direct children)." + }, + "excludes": { "type": "object", "properties": { - "kind": { + "items": { "type": "string" } }, "required": [ - "kind" - ] + "items" + ], + "description": "Optional glob patterns or paths relative to `root` to exclude from\nchange reporting." }, - { + "includes": { "type": "object", "properties": { - "kind": { - "type": "string" - }, - "chat": { - "type": "string" - }, - "turnId": { + "items": { "type": "string" } }, "required": [ - "kind", - "chat", - "turnId" - ] + "items" + ], + "description": "Optional glob patterns or paths relative to `root` to restrict\nchange reporting to. Omit to report every change under `root`\nsubject to `excludes`." + } + }, + "required": [ + "root", + "recursive" + ] + }, + "ResourceChange": { + "type": "object", + "description": "A single change observed by a resource watcher.", + "properties": { + "uri": { + "$ref": "#/$defs/URI", + "description": "The URI of the resource that changed." + }, + "type": { + "$ref": "#/$defs/ResourceChangeType", + "description": "The kind of change observed." + } + }, + "required": [ + "uri", + "type" + ] + }, + "StringOrMarkdown": { + "oneOf": [ + { + "type": "string" }, { "type": "object", "properties": { - "kind": { - "type": "string" - }, - "chat": { - "type": "string" - }, - "toolCallId": { + "markdown": { "type": "string" } }, "required": [ - "kind", - "chat", - "toolCallId" + "markdown" ] } - ] + ], + "description": "A string that may optionally be rendered as Markdown.\n\n- A plain `string` is rendered as-is (no Markdown processing).\n- An object with `{ markdown: string }` is rendered with Markdown formatting." }, - "ChatInputQuestion": { + "SessionInputQuestion": { "oneOf": [ { - "$ref": "#/$defs/ChatInputTextQuestion" + "$ref": "#/$defs/SessionInputTextQuestion" }, { - "$ref": "#/$defs/ChatInputNumberQuestion" + "$ref": "#/$defs/SessionInputNumberQuestion" }, { - "$ref": "#/$defs/ChatInputBooleanQuestion" + "$ref": "#/$defs/SessionInputBooleanQuestion" }, { - "$ref": "#/$defs/ChatInputSingleSelectQuestion" + "$ref": "#/$defs/SessionInputSingleSelectQuestion" }, { - "$ref": "#/$defs/ChatInputMultiSelectQuestion" + "$ref": "#/$defs/SessionInputMultiSelectQuestion" } ], - "description": "One question within a chat input request." + "description": "One question within a session input request." }, - "ChatInputAnswerValue": { + "SessionInputAnswerValue": { "oneOf": [ { - "$ref": "#/$defs/ChatInputTextAnswerValue" + "$ref": "#/$defs/SessionInputTextAnswerValue" }, { - "$ref": "#/$defs/ChatInputNumberAnswerValue" + "$ref": "#/$defs/SessionInputNumberAnswerValue" }, { - "$ref": "#/$defs/ChatInputBooleanAnswerValue" + "$ref": "#/$defs/SessionInputBooleanAnswerValue" }, { - "$ref": "#/$defs/ChatInputSelectedAnswerValue" + "$ref": "#/$defs/SessionInputSelectedAnswerValue" }, { - "$ref": "#/$defs/ChatInputSelectedManyAnswerValue" + "$ref": "#/$defs/SessionInputSelectedManyAnswerValue" } ] }, - "ChatInputAnswer": { + "SessionInputAnswer": { "oneOf": [ { - "$ref": "#/$defs/ChatInputAnswered" + "$ref": "#/$defs/SessionInputAnswered" }, { - "$ref": "#/$defs/ChatInputSkipped" + "$ref": "#/$defs/SessionInputSkipped" } ], "description": "Draft, submitted, or skipped answer for one question." @@ -6181,6 +5730,108 @@ ], "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output, and\n`ToolResultSubagentContent` for subagent sessions (AHP extensions)." }, + "ChildCustomizationType": { + "oneOf": [ + {}, + { + "$ref": "#/$defs/CustomizationType.Agent" + }, + { + "$ref": "#/$defs/CustomizationType.Skill" + }, + { + "$ref": "#/$defs/CustomizationType.Prompt" + }, + { + "$ref": "#/$defs/CustomizationType.Rule" + }, + { + "$ref": "#/$defs/CustomizationType.Hook" + }, + { + "$ref": "#/$defs/CustomizationType.McpServer" + } + ], + "description": "Customization types that appear as children of a\n{@link PluginCustomization} or {@link DirectoryCustomization}." + }, + "CustomizationLoadState": { + "oneOf": [ + {}, + { + "$ref": "#/$defs/CustomizationLoadingState" + }, + { + "$ref": "#/$defs/CustomizationLoadedState" + }, + { + "$ref": "#/$defs/CustomizationDegradedState" + }, + { + "$ref": "#/$defs/CustomizationErrorState" + } + ], + "description": "Discriminated load state for a container customization\n({@link PluginCustomization} or {@link DirectoryCustomization})." + }, + "ChildCustomization": { + "oneOf": [ + {}, + { + "$ref": "#/$defs/AgentCustomization" + }, + { + "$ref": "#/$defs/SkillCustomization" + }, + { + "$ref": "#/$defs/PromptCustomization" + }, + { + "$ref": "#/$defs/RuleCustomization" + }, + { + "$ref": "#/$defs/HookCustomization" + }, + { + "$ref": "#/$defs/McpServerCustomization" + } + ], + "description": "Child customizations that live inside a {@link PluginCustomization} or\n{@link DirectoryCustomization}." + }, + "Customization": { + "oneOf": [ + {}, + { + "$ref": "#/$defs/PluginCustomization" + }, + { + "$ref": "#/$defs/DirectoryCustomization" + }, + { + "$ref": "#/$defs/McpServerCustomization" + } + ], + "description": "A top-level customization active in a session. Either a container\n({@link PluginCustomization} or {@link DirectoryCustomization}) whose\nleaf customizations live in its\n{@link ContainerCustomizationBase.children | `children`} array, or a\nbare {@link McpServerCustomization} surfaced directly by the host." + }, + "McpServerState": { + "oneOf": [ + {}, + { + "$ref": "#/$defs/McpServerStartingState" + }, + { + "$ref": "#/$defs/McpServerReadyState" + }, + { + "$ref": "#/$defs/McpServerAuthRequiredState" + }, + { + "$ref": "#/$defs/McpServerErrorState" + }, + { + "$ref": "#/$defs/McpServerStoppedState" + } + ], + "description": "Discriminated union of all MCP server lifecycle states.\nDiscriminated by `kind` (a {@link McpServerStatus} value)." + }, "TerminalClaim": { "oneOf": [ { @@ -6229,133 +5880,121 @@ "$ref": "#/$defs/SessionCreationFailedAction" }, { - "$ref": "#/$defs/SessionChatAddedAction" - }, - { - "$ref": "#/$defs/SessionChatRemovedAction" + "$ref": "#/$defs/SessionTurnStartedAction" }, { - "$ref": "#/$defs/SessionChatUpdatedAction" - }, - { - "$ref": "#/$defs/SessionDefaultChatChangedAction" - }, - { - "$ref": "#/$defs/SessionTitleChangedAction" - }, - { - "$ref": "#/$defs/SessionModelChangedAction" + "$ref": "#/$defs/SessionDeltaAction" }, { - "$ref": "#/$defs/SessionAgentChangedAction" + "$ref": "#/$defs/SessionResponsePartAction" }, { - "$ref": "#/$defs/SessionServerToolsChangedAction" + "$ref": "#/$defs/SessionToolCallStartAction" }, { - "$ref": "#/$defs/SessionActiveClientChangedAction" + "$ref": "#/$defs/SessionToolCallDeltaAction" }, { - "$ref": "#/$defs/SessionActiveClientToolsChangedAction" + "$ref": "#/$defs/SessionToolCallReadyAction" }, { - "$ref": "#/$defs/SessionCustomizationsChangedAction" + "$ref": "#/$defs/SessionToolCallConfirmedAction" }, { - "$ref": "#/$defs/SessionCustomizationToggledAction" + "$ref": "#/$defs/SessionToolCallCompleteAction" }, { - "$ref": "#/$defs/SessionCustomizationUpdatedAction" + "$ref": "#/$defs/SessionToolCallResultConfirmedAction" }, { - "$ref": "#/$defs/SessionCustomizationRemovedAction" + "$ref": "#/$defs/SessionToolCallContentChangedAction" }, { - "$ref": "#/$defs/SessionMcpServerStateChangedAction" + "$ref": "#/$defs/SessionTurnCompleteAction" }, { - "$ref": "#/$defs/SessionIsReadChangedAction" + "$ref": "#/$defs/SessionTurnCancelledAction" }, { - "$ref": "#/$defs/SessionIsArchivedChangedAction" + "$ref": "#/$defs/SessionErrorAction" }, { - "$ref": "#/$defs/SessionActivityChangedAction" + "$ref": "#/$defs/SessionTitleChangedAction" }, { - "$ref": "#/$defs/SessionChangesetsChangedAction" + "$ref": "#/$defs/SessionUsageAction" }, { - "$ref": "#/$defs/SessionConfigChangedAction" + "$ref": "#/$defs/SessionReasoningAction" }, { - "$ref": "#/$defs/SessionMetaChangedAction" + "$ref": "#/$defs/SessionModelChangedAction" }, { - "$ref": "#/$defs/ChatTurnStartedAction" + "$ref": "#/$defs/SessionAgentChangedAction" }, { - "$ref": "#/$defs/ChatDeltaAction" + "$ref": "#/$defs/SessionServerToolsChangedAction" }, { - "$ref": "#/$defs/ChatResponsePartAction" + "$ref": "#/$defs/SessionActiveClientChangedAction" }, { - "$ref": "#/$defs/ChatToolCallStartAction" + "$ref": "#/$defs/SessionActiveClientToolsChangedAction" }, { - "$ref": "#/$defs/ChatToolCallDeltaAction" + "$ref": "#/$defs/SessionPendingMessageSetAction" }, { - "$ref": "#/$defs/ChatToolCallReadyAction" + "$ref": "#/$defs/SessionPendingMessageRemovedAction" }, { - "$ref": "#/$defs/ChatToolCallConfirmedAction" + "$ref": "#/$defs/SessionQueuedMessagesReorderedAction" }, { - "$ref": "#/$defs/ChatToolCallCompleteAction" + "$ref": "#/$defs/SessionInputRequestedAction" }, { - "$ref": "#/$defs/ChatToolCallResultConfirmedAction" + "$ref": "#/$defs/SessionInputAnswerChangedAction" }, { - "$ref": "#/$defs/ChatToolCallContentChangedAction" + "$ref": "#/$defs/SessionInputCompletedAction" }, { - "$ref": "#/$defs/ChatTurnCompleteAction" + "$ref": "#/$defs/SessionCustomizationsChangedAction" }, { - "$ref": "#/$defs/ChatTurnCancelledAction" + "$ref": "#/$defs/SessionCustomizationToggledAction" }, { - "$ref": "#/$defs/ChatErrorAction" + "$ref": "#/$defs/SessionCustomizationUpdatedAction" }, { - "$ref": "#/$defs/ChatUsageAction" + "$ref": "#/$defs/SessionCustomizationRemovedAction" }, { - "$ref": "#/$defs/ChatReasoningAction" + "$ref": "#/$defs/SessionMcpServerStateChangedAction" }, { - "$ref": "#/$defs/ChatPendingMessageSetAction" + "$ref": "#/$defs/SessionTruncatedAction" }, { - "$ref": "#/$defs/ChatPendingMessageRemovedAction" + "$ref": "#/$defs/SessionIsReadChangedAction" }, { - "$ref": "#/$defs/ChatQueuedMessagesReorderedAction" + "$ref": "#/$defs/SessionIsArchivedChangedAction" }, { - "$ref": "#/$defs/ChatInputRequestedAction" + "$ref": "#/$defs/SessionActivityChangedAction" }, { - "$ref": "#/$defs/ChatInputAnswerChangedAction" + "$ref": "#/$defs/SessionChangesetsChangedAction" }, { - "$ref": "#/$defs/ChatInputCompletedAction" + "$ref": "#/$defs/SessionConfigChangedAction" }, { - "$ref": "#/$defs/ChatTruncatedAction" + "$ref": "#/$defs/SessionMetaChangedAction" }, { "$ref": "#/$defs/ChangesetStatusChangedAction" diff --git a/schema/commands.schema.json b/schema/commands.schema.json index a3b0099f..6d7a4bdd 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -907,11 +907,11 @@ }, "FetchTurnsParams": { "type": "object", - "description": "Fetches historical turns for a chat. Used for lazy loading of conversation\nhistory.", + "description": "Fetches historical turns for a session. Used for lazy loading of conversation\nhistory.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "Chat URI" + "description": "Session URI" }, "before": { "type": "string", @@ -953,7 +953,7 @@ "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The chat URI the completion is being requested for." + "description": "The session URI the completion is being requested for." }, "kind": { "$ref": "#/$defs/CompletionItemKind", @@ -1017,71 +1017,6 @@ "items" ] }, - "ChatForkSource": { - "type": "object", - "description": "Identifies a source chat and turn to fork from.", - "properties": { - "chat": { - "$ref": "#/$defs/URI", - "description": "URI of the existing chat to fork from" - }, - "turnId": { - "type": "string", - "description": "Turn ID in the source chat; content up to and including this turn's response is copied" - } - }, - "required": [ - "chat", - "turnId" - ] - }, - "CreateChatParams": { - "type": "object", - "description": "Creates a new chat within a session.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "Session URI containing the new chat." - }, - "chat": { - "$ref": "#/$defs/URI", - "description": "Chat URI (client-chosen, e.g. `ahp-chat:/`)." - }, - "initialMessage": { - "$ref": "#/$defs/Message", - "description": "Optional initial message for the new chat." - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override." - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override." - }, - "source": { - "$ref": "#/$defs/ChatForkSource", - "description": "Optional source chat and turn to fork from." - } - }, - "required": [ - "channel", - "chat" - ] - }, - "DisposeChatParams": { - "type": "object", - "description": "Disposes a chat and cleans up server-side resources.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "Channel URI this command targets." - } - }, - "required": [ - "channel" - ] - }, "CreateTerminalParams": { "type": "object", "description": "Creates a new terminal on the server.\n\nAfter creation, the client should subscribe to the terminal URI to receive\nstate updates. The server dispatches `root/terminalsChanged` to update the\nroot terminal list.", @@ -1632,7 +1567,7 @@ "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`)" + "description": "The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`)" }, "state": { "oneOf": [ @@ -1830,6 +1765,24 @@ "values" ] }, + "PendingMessage": { + "type": "object", + "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this pending message" + }, + "message": { + "$ref": "#/$defs/Message", + "description": "The message that will start the next turn" + } + }, + "required": [ + "id", + "message" + ] + }, "SessionState": { "type": "object", "description": "Full state for a single session, loaded when a client subscribes to the session's URI.", @@ -1857,16 +1810,34 @@ "$ref": "#/$defs/SessionActiveClient", "description": "The client currently providing tools and interactive capabilities to this session" }, - "chats": { + "turns": { "type": "array", "items": { - "$ref": "#/$defs/ChatSummary" + "$ref": "#/$defs/Turn" }, - "description": "Catalog of chats in this session." + "description": "Completed turns" }, - "defaultChat": { - "$ref": "#/$defs/URI", - "description": "The chat that receives input when the user addresses the session without\nselecting a specific chat. This is a UI routing hint, not a hierarchy\nmarker — chats remain equal peers at the protocol level. Hosts MAY change\nthis over the session's lifetime." + "activeTurn": { + "$ref": "#/$defs/ActiveTurn", + "description": "Currently in-progress turn" + }, + "steeringMessage": { + "$ref": "#/$defs/PendingMessage", + "description": "Message to inject into the current turn at a convenient point" + }, + "queuedMessages": { + "type": "array", + "items": { + "$ref": "#/$defs/PendingMessage" + }, + "description": "Messages to send automatically as new turns after the current turn finishes" + }, + "inputRequests": { + "type": "array", + "items": { + "$ref": "#/$defs/SessionInputRequest" + }, + "description": "Requests for user input that are currently blocking or informing session progress" }, "config": { "$ref": "#/$defs/SessionConfigState", @@ -1895,7 +1866,7 @@ "required": [ "summary", "lifecycle", - "chats" + "turns" ] }, "SessionActiveClient": { @@ -1950,7 +1921,6 @@ }, "SessionSummary": { "type": "object", - "description": "Lightweight catalog entry summarizing one session. Surfaced via\n{@link RootChannelCommands.listSessions | `root/listSessions`} and\n`root/sessionAdded`/`root/sessionSummaryChanged` notifications.\n\n**Aggregation across chats.** Once a session contains more than one chat,\nseveral `SessionSummary` fields are derived from the underlying\n{@link SessionState.chats | chat catalog}. Producers SHOULD follow these\nrules so clients that only consume the session summary (e.g. a session\nlist) still see meaningful state:\n\n- `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` /\n `Error` — bits 0–4) from the\n {@link SessionState.defaultChat | default chat} when present, else from\n the most recently modified chat. **Promote** `InputNeeded` whenever any\n chat in the session needs input, and **promote** `Error` whenever any\n chat is in an error state — both override the default-chat bits. The\n orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped.\n- `activity`: mirror the activity string of the default chat, or of the\n chat currently driving the promoted status bits when a non-default chat\n wins (e.g. the chat that raised `InputNeeded`).\n- `modifiedAt`: the max of all chats' `modifiedAt`.\n- `model` / `agent`: the session-level selection. Per-chat overrides are\n surfaced on individual {@link ChatSummary} entries, not aggregated up.\n- `workingDirectory`: the session-level **default**. Individual chats MAY\n override via {@link ChatSummary.workingDirectory}; aggregating these up\n is meaningless and SHOULD NOT be attempted.\n- `changes`: optional roll-up across all chats. Producers MAY sum the\n per-chat changeset stats or report the most expensive chat's stats —\n whichever is cheaper for the host to compute.\n\nSessions with a single chat trivially satisfy all of the above (the chat's\nvalues pass through unchanged). The rules only matter once a session\ncarries multiple chats.", "properties": { "resource": { "$ref": "#/$defs/URI", @@ -1994,7 +1964,7 @@ }, "workingDirectory": { "$ref": "#/$defs/URI", - "description": "The default working directory URI for this session. Individual chats\nMAY override via {@link ChatSummary.workingDirectory | their own\n`workingDirectory`}; this field acts as the fallback for any chat that\ndoes not." + "description": "The working directory URI for this session" }, "changes": { "$ref": "#/$defs/ChangesSummary", @@ -2178,1774 +2148,1570 @@ "values" ] }, - "ToolDefinition": { + "SessionInputOption": { "type": "object", - "description": "Describes a tool available in a session, provided by either the server or the active client.", + "description": "A choice in a select-style question.", "properties": { - "name": { + "id": { "type": "string", - "description": "Unique tool identifier" + "description": "Stable option identifier; for MCP enum values this is the enum string" }, - "title": { + "label": { "type": "string", - "description": "Human-readable display name" + "description": "Display label" }, "description": { "type": "string", - "description": "Description of what the tool does" - }, - "inputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." - }, - "outputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." - }, - "annotations": { - "$ref": "#/$defs/ToolAnnotations", - "description": "Behavioral hints about the tool. All properties are advisory." + "description": "Optional secondary text" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." + "recommended": { + "type": "boolean", + "description": "Whether this option is the recommended/default choice" } }, "required": [ - "name" + "id", + "label" ] }, - "ToolAnnotations": { + "SessionInputQuestionBase": { "type": "object", - "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "title": { + "id": { "type": "string", - "description": "Alternate human-readable title" - }, - "readOnlyHint": { - "type": "boolean", - "description": "Tool does not modify its environment (default: false)" + "description": "Stable question identifier used as the key in `answers`" }, - "destructiveHint": { - "type": "boolean", - "description": "Tool may perform destructive updates (default: true)" + "title": { + "type": "string", + "description": "Short display title" }, - "idempotentHint": { - "type": "boolean", - "description": "Repeated calls with the same arguments have no additional effect (default: false)" + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "openWorldHint": { + "required": { "type": "boolean", - "description": "Tool may interact with external entities (default: true)" + "description": "Whether the user must answer this question to accept the request" } - } + }, + "required": [ + "id", + "message" + ] }, - "CustomizationBase": { + "SessionInputTextQuestion": { "type": "object", - "description": "Fields shared by every customization variant.", + "description": "Text question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "Stable question identifier used as the key in `answers`" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "title": { + "type": "string", + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Prompt shown to the user" }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - } - }, - "required": [ - "id", - "uri", - "name" - ] - }, - "CustomizationLoadingState": { - "type": "object", - "description": "Container is being loaded by the host.", - "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loading" - } - }, - "required": [ - "kind" - ] - }, - "CustomizationLoadedState": { - "type": "object", - "description": "Container loaded successfully.", - "properties": { "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loaded" + "$ref": "#/$defs/SessionInputQuestionKind.Text" + }, + "format": { + "type": "string", + "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + }, + "min": { + "type": "number", + "description": "Minimum string length" + }, + "max": { + "type": "number", + "description": "Maximum string length" + }, + "defaultValue": { + "type": "string", + "description": "Default text" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationDegradedState": { + "SessionInputNumberQuestion": { "type": "object", - "description": "Container partially loaded but has warnings.", + "description": "Numeric question within a session input request.", "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Degraded" + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" }, - "message": { + "title": { "type": "string", - "description": "Human-readable description of the warning." - } - }, - "required": [ - "kind", - "message" - ] - }, - "CustomizationErrorState": { - "type": "object", - "description": "Container failed to load.", - "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Error" + "description": "Short display title" }, "message": { "type": "string", - "description": "Human-readable error message." + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, + "kind": { + "oneOf": [ + { + "$ref": "#/$defs/SessionInputQuestionKind.Number" + }, + { + "$ref": "#/$defs/SessionInputQuestionKind.Integer" + } + ] + }, + "min": { + "type": "number", + "description": "Minimum value" + }, + "max": { + "type": "number", + "description": "Maximum value" + }, + "defaultValue": { + "type": "number", + "description": "Default numeric value" } }, "required": [ - "kind", - "message" + "id", + "message", + "kind" ] }, - "ContainerCustomizationBase": { + "SessionInputBooleanQuestion": { "type": "object", - "description": "Fields shared by container customizations.", + "description": "Boolean question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable question identifier used as the key in `answers`" }, - "name": { + "title": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Short display title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.Boolean" }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" - }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "defaultValue": { + "type": "boolean", + "description": "Default boolean value" } }, "required": [ "id", - "uri", - "name", - "enabled" + "message", + "kind" ] }, - "PluginCustomization": { + "SessionInputSingleSelectQuestion": { "type": "object", - "description": "An [Open Plugins](https://open-plugins.com/) plugin.", + "description": "Single-select question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable question identifier used as the key in `answers`" }, - "name": { + "title": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Short display title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.SingleSelect" }, - "children": { + "options": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Options the user may select from" }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text instead of selecting an option" } }, "required": [ "id", - "uri", - "name", - "enabled", - "type" + "message", + "kind", + "options" ] }, - "ClientPluginCustomization": { + "SessionInputMultiSelectQuestion": { "type": "object", - "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", + "description": "Multi-select question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable question identifier used as the key in `answers`" }, - "name": { + "title": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Short display title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.MultiSelect" }, - "children": { + "options": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Options the user may select from" }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text in addition to selecting options" }, - "nonce": { - "type": "string", - "description": "Opaque version token used by the host to detect changes." + "min": { + "type": "number", + "description": "Minimum selected item count" + }, + "max": { + "type": "number", + "description": "Maximum selected item count" } }, "required": [ "id", - "uri", - "name", - "enabled", - "type" + "message", + "kind", + "options" ] }, - "DirectoryCustomization": { + "SessionInputRequest": { "type": "object", - "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", + "description": "A live request for user input.\n\nThe server creates or replaces requests with `session/inputRequested`.\nClients sync drafts with `session/inputAnswerChanged` and complete requests\nwith `session/inputCompleted`.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable request identifier" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Display message for the request as a whole" }, - "icons": { + "url": { + "$ref": "#/$defs/URI", + "description": "URL the user should review or open, for URL-style elicitations" + }, + "questions": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/SessionInputQuestion" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Ordered questions to ask the user" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." - }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" + "answers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/SessionInputAnswer" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Directory" + "description": "Current draft or submitted answers, keyed by question ID" + } + }, + "required": [ + "id" + ] + }, + "SessionInputTextAnswerValue": { + "type": "object", + "description": "Value captured for one answer.", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Text" }, - "contents": { - "$ref": "#/$defs/ChildCustomizationType", - "description": "Which child customization type this directory holds." + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputNumberAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Number" }, - "writable": { - "type": "boolean", - "description": "Whether clients may write into this directory." + "value": { + "type": "number" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type", - "contents", - "writable" + "kind", + "value" ] }, - "AgentCustomization": { + "SessionInputBooleanAnswerValue": { "type": "object", - "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Boolean" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "value": { + "type": "boolean" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputSelectedAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Selected" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "string" }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Agent" - }, - "description": { - "type": "string", - "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." + "description": "Free-form text entered instead of selecting an option" } }, "required": [ - "id", - "uri", - "name", - "type" + "kind", + "value" ] }, - "SkillCustomization": { + "SessionInputSelectedManyAnswerValue": { "type": "object", - "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.SelectedMany" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "array", + "items": { + "type": "string" + } }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Skill" + "description": "Free-form text entered in addition to selected options" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputAnswered": { + "type": "object", + "properties": { + "state": { + "oneOf": [ + { + "$ref": "#/$defs/SessionInputAnswerState.Draft" + }, + { + "$ref": "#/$defs/SessionInputAnswerState.Submitted" + } + ], + "description": "Answer state" }, - "description": { - "type": "string", - "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." + "value": { + "$ref": "#/$defs/SessionInputAnswerValue", + "description": "Answer value" + } + }, + "required": [ + "state", + "value" + ] + }, + "SessionInputSkipped": { + "type": "object", + "properties": { + "state": { + "$ref": "#/$defs/SessionInputAnswerState.Skipped", + "description": "Answer state" }, - "disableModelInvocation": { - "type": "boolean", - "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." + "freeformValues": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Free-form reason or value captured while skipping, if any" } }, "required": [ - "id", - "uri", - "name", - "type" + "state" ] }, - "PromptCustomization": { + "Turn": { "type": "object", - "description": "A prompt contributed by a plugin or directory.", + "description": "A completed request/response cycle.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" }, - "type": { - "$ref": "#/$defs/CustomizationType.Prompt" + "state": { + "$ref": "#/$defs/TurnState", + "description": "How the turn ended" }, - "description": { - "type": "string", - "description": "Short description of what the prompt does." + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details if state is `'error'`" } }, "required": [ "id", - "uri", - "name", - "type" + "message", + "responseParts", + "usage", + "state" ] }, - "RuleCustomization": { + "ActiveTurn": { "type": "object", - "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", + "description": "An in-progress turn — the assistant is actively streaming.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." }, - "type": { - "$ref": "#/$defs/CustomizationType.Rule" - }, - "description": { + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" + } + }, + "required": [ + "id", + "message", + "responseParts", + "usage" + ] + }, + "Message": { + "type": "object", + "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", + "properties": { + "text": { "type": "string", - "description": "Description of what the rule enforces." + "description": "Message text" }, - "alwaysApply": { - "type": "boolean", - "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." + "origin": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + }, + "required": [ + "kind" + ], + "description": "The origin of the message" }, - "globs": { + "attachments": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/MessageAttachment" }, - "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." + "description": "File/selection attachments" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." } }, "required": [ - "id", - "uri", - "name", - "type" + "text", + "origin" ] }, - "HookCustomization": { + "MessageAttachmentBase": { "type": "object", - "description": "A hook manifest contributed by a plugin or directory.", + "description": "Common fields shared by all {@link MessageAttachment} variants.", "properties": { - "id": { + "label": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "name": { + "displayKind": { "type": "string", - "description": "Human-readable name." + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + } + }, + "required": [ + "label" + ] + }, + "SimpleMessageAttachment": { + "type": "object", + "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", + "properties": { + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/CustomizationType.Hook" + "$ref": "#/$defs/MessageAttachmentKind.Simple", + "description": "Discriminant" + }, + "modelRepresentation": { + "type": "string", + "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." } }, "required": [ - "id", - "uri", - "name", + "label", "type" ] }, - "McpServerCustomization": { + "MessageEmbeddedResourceAttachment": { "type": "object", - "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", + "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", "properties": { - "id": { + "label": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "name": { + "displayKind": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, "type": { - "$ref": "#/$defs/CustomizationType.McpServer" - }, - "enabled": { - "type": "boolean", - "description": "Whether this MCP server is currently enabled." + "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", + "description": "Discriminant" }, - "state": { - "$ref": "#/$defs/McpServerState", - "description": "Current lifecycle state of the MCP server." + "data": { + "type": "string", + "description": "Base64-encoded binary data" }, - "channel": { - "$ref": "#/$defs/URI", - "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + "contentType": { + "type": "string", + "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" }, - "mcpApp": { - "$ref": "#/$defs/McpServerCustomizationApps", - "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "id", - "uri", - "name", + "label", "type", - "enabled", - "state" + "data", + "contentType" ] }, - "McpServerCustomizationApps": { + "MessageResourceAttachment": { "type": "object", - "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", + "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", "properties": { - "capabilities": { - "$ref": "#/$defs/AhpMcpUiHostCapabilities", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Resource", + "description": "Discriminant" + }, + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "capabilities" + "label", + "uri", + "type" ] }, - "AhpMcpUiHostCapabilities": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { - "serverTools": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `tools/*` methods to the upstream server." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "serverResources": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `resources/*` methods to the upstream server." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "logging": { + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { "type": "object", "additionalProperties": {}, - "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, - "sampling": { - "type": "object", - "properties": { - "tools": { - "type": "string" - } + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Annotations", + "description": "Discriminant" + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." + }, + "annotationIds": { + "type": "array", + "items": { + "type": "string" }, - "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } - } + }, + "required": [ + "label", + "type", + "resource" + ] }, - "McpServerStartingState": { + "MarkdownResponsePart": { "type": "object", - "description": "Server is registered with the host but has not yet started.", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Starting" + "$ref": "#/$defs/ResponsePartKind.Markdown", + "description": "Discriminant" + }, + "id": { + "type": "string", + "description": "Part identifier, used by `session/delta` to target this part for content appends" + }, + "content": { + "type": "string", + "description": "Markdown content" } }, "required": [ - "kind" + "kind", + "id", + "content" ] }, - "McpServerReadyState": { + "ResourceReponsePart": { "type": "object", - "description": "Server is running and serving requests.", + "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" + }, "kind": { - "$ref": "#/$defs/McpServerStatus.Ready" + "$ref": "#/$defs/ResponsePartKind.ContentRef", + "description": "Discriminant" } }, "required": [ + "uri", "kind" ] }, - "McpServerAuthRequiredState": { + "ToolCallResponsePart": { "type": "object", - "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", + "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.AuthRequired" - }, - "reason": { - "$ref": "#/$defs/McpAuthRequiredReason", - "description": "Why authentication is required." - }, - "resource": { - "$ref": "#/$defs/ProtectedResourceMetadata", - "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." - }, - "requiredScopes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." + "$ref": "#/$defs/ResponsePartKind.ToolCall", + "description": "Discriminant" }, - "description": { - "type": "string", - "description": "Human-readable hint, typically from the OAuth `error_description`." + "toolCall": { + "$ref": "#/$defs/ToolCallState", + "description": "Full tool call lifecycle state" } }, "required": [ "kind", - "reason", - "resource" + "toolCall" ] }, - "McpServerErrorState": { + "ReasoningResponsePart": { "type": "object", - "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", + "description": "Reasoning/thinking content from the model.", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Error" + "$ref": "#/$defs/ResponsePartKind.Reasoning", + "description": "Discriminant" }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details." + "id": { + "type": "string", + "description": "Part identifier, used by `session/reasoning` to target this part for content appends" + }, + "content": { + "type": "string", + "description": "Accumulated reasoning text" } }, "required": [ "kind", - "error" + "id", + "content" ] }, - "McpServerStoppedState": { + "SystemNotificationResponsePart": { "type": "object", - "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", + "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Stopped" + "$ref": "#/$defs/ResponsePartKind.SystemNotification", + "description": "Discriminant" + }, + "content": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "The text of the system notification" } }, "required": [ - "kind" + "kind", + "content" ] }, - "ChatState": { + "ConfirmationOption": { "type": "object", - "description": "Full state for a single chat, loaded when a client subscribes to the chat's\nURI.\n\nThe lightweight catalog representation of a chat is {@link ChatSummary},\ncarried in {@link SessionState.chats | `SessionState.chats`}. `ChatState`\n**denormalizes** every {@link ChatSummary} field directly onto itself so\nsubscribers receive one flat object instead of having to merge a nested\n`summary` sub-object. Producers MUST keep the two representations\nconsistent: any change to the inlined fields below SHOULD also be\nannounced on the parent session via the matching\n{@link SessionChatUpdatedAction | `session/chatUpdated`} action.", + "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { + "id": { "type": "string", - "description": "Human-readable description of what the chat is currently doing" + "description": "Unique identifier for the option, returned in the confirmed action" }, - "modifiedAt": { + "label": { "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nHosts MAY override this for individual chats — for example, to give a\nsubordinate chat its own git worktree so multiple chats in a session can\nmake independent edits that the orchestrator later merges back." - }, - "turns": { - "type": "array", - "items": { - "$ref": "#/$defs/Turn" - }, - "description": "Completed turns" - }, - "activeTurn": { - "$ref": "#/$defs/ActiveTurn", - "description": "Currently in-progress turn" - }, - "steeringMessage": { - "$ref": "#/$defs/PendingMessage", - "description": "Message to inject into the current turn at a convenient point" - }, - "queuedMessages": { - "type": "array", - "items": { - "$ref": "#/$defs/PendingMessage" - }, - "description": "Messages to send automatically as new turns after the current turn finishes" + "description": "Human-readable label displayed to the user" }, - "inputRequests": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputRequest" - }, - "description": "Requests for user input that are currently blocking or informing chat progress" + "kind": { + "$ref": "#/$defs/ConfirmationOptionKind", + "description": "Whether this option represents an approval or denial" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this chat." + "group": { + "type": "number", + "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." } }, "required": [ - "resource", - "title", - "status", - "modifiedAt", - "turns" + "id", + "label", + "kind" ] }, - "ChatSummary": { + "ToolCallClientContributor": { "type": "object", - "description": "Lightweight catalog entry for a chat, carried in\n{@link SessionState.chats | `SessionState.chats`}. The full conversation\nlives in {@link ChatState}, which inlines (denormalizes) every field below.", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { - "type": "string", - "description": "Human-readable description of what the chat is currently doing" + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.Client" }, - "modifiedAt": { + "clientId": { "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nSee {@link ChatState.workingDirectory} for usage notes." + "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `session/toolCallComplete` with the result." } }, "required": [ - "resource", - "title", - "status", - "modifiedAt" + "kind", + "clientId" ] }, - "PendingMessage": { + "ToolCallMcpContributor": { "type": "object", - "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", "properties": { - "id": { - "type": "string", - "description": "Unique identifier for this pending message" + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.MCP" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that will start the next turn" + "customizationId": { + "type": "string", + "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." } }, "required": [ - "id", - "message" + "kind", + "customizationId" ] }, - "ChatInputOption": { + "ToolCallBase": { "type": "object", - "description": "A choice in a select-style question.", + "description": "Metadata common to all tool call states.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable option identifier; for MCP enum values this is the enum string" + "description": "Unique tool call identifier" }, - "label": { + "toolName": { "type": "string", - "description": "Display label" + "description": "Internal tool name (for debugging/logging)" }, - "description": { + "displayName": { "type": "string", - "description": "Optional secondary text" + "description": "Human-readable tool name" }, - "recommended": { - "type": "boolean", - "description": "Whether this option is the recommended/default choice" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." } }, "required": [ - "id", - "label" + "toolCallId", + "toolName", + "displayName" ] }, - "ChatInputQuestionBase": { + "ToolCallParameterFields": { "type": "object", + "description": "Properties available once tool call parameters are fully received.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "message": { + "toolInput": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "description": "Raw tool input" } }, "required": [ - "id", - "message" + "invocationMessage" ] }, - "ChatInputTextQuestion": { + "ToolCallResult": { "type": "object", - "description": "Text question within a chat input request.", + "description": "Tool execution result details, available after execution completes.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" - }, - "message": { - "type": "string", - "description": "Prompt shown to the user" - }, - "required": { + "success": { "type": "boolean", - "description": "Whether the user must answer this question to accept the request" - }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Text" + "description": "Whether the tool succeeded" }, - "format": { - "type": "string", - "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" }, - "min": { - "type": "number", - "description": "Minimum string length" + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." }, - "max": { - "type": "number", - "description": "Maximum string length" + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." }, - "defaultValue": { - "type": "string", - "description": "Default text" + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" } }, "required": [ - "id", - "message", - "kind" + "success", + "pastTenseMessage" ] }, - "ChatInputNumberQuestion": { + "ToolCallStreamingState": { "type": "object", - "description": "Numeric question within a chat input request.", + "description": "LM is streaming the tool call parameters.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputQuestionKind.Number" - }, - { - "$ref": "#/$defs/ChatInputQuestionKind.Integer" - } - ] + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "min": { - "type": "number", - "description": "Minimum value" + "status": { + "$ref": "#/$defs/ToolCallStatus.Streaming" }, - "max": { - "type": "number", - "description": "Maximum value" + "partialInput": { + "type": "string", + "description": "Partial parameters accumulated so far" }, - "defaultValue": { - "type": "number", - "description": "Default numeric value" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Progress message shown while parameters are streaming" } }, "required": [ - "id", - "message", - "kind" + "toolCallId", + "toolName", + "displayName", + "status" ] }, - "ChatInputBooleanQuestion": { + "ToolCallPendingConfirmationState": { "type": "object", - "description": "Boolean question within a chat input request.", + "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Boolean" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "defaultValue": { - "type": "boolean", - "description": "Default boolean value" - } - }, - "required": [ - "id", - "message", - "kind" - ] - }, - "ChatInputSingleSelectQuestion": { - "type": "object", - "description": "Single-select question within a chat input request.", - "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "title": { + "toolInput": { "type": "string", - "description": "Short display title" + "description": "Raw tool input" }, - "message": { - "type": "string", - "description": "Prompt shown to the user" + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "confirmationTitle": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.SingleSelect" + "edits": { + "type": "object", + "properties": { + "items": { + "type": "string" + } + }, + "required": [ + "items" + ], + "description": "File edits that this tool call will perform, for preview before confirmation" + }, + "editable": { + "type": "boolean", + "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" }, "options": { "type": "array", "items": { - "$ref": "#/$defs/ChatInputOption" + "$ref": "#/$defs/ConfirmationOption" }, - "description": "Options the user may select from" - }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text instead of selecting an option" + "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." } }, "required": [ - "id", - "message", - "kind", - "options" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status" ] }, - "ChatInputMultiSelectQuestion": { + "ToolCallRunningState": { "type": "object", - "description": "Multi-select question within a chat input request.", + "description": "Tool is actively executing.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" - }, - "message": { + "toolCallId": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" - }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.MultiSelect" - }, - "options": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputOption" - }, - "description": "Options the user may select from" - }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text in addition to selecting options" - }, - "min": { - "type": "number", - "description": "Minimum selected item count" + "description": "Unique tool call identifier" }, - "max": { - "type": "number", - "description": "Maximum selected item count" - } - }, - "required": [ - "id", - "message", - "kind", - "options" - ] - }, - "ChatInputRequest": { - "type": "object", - "description": "A live request for user input.\n\nThe server creates or replaces requests with `chat/inputRequested`.\nClients sync drafts with `chat/inputAnswerChanged` and complete requests\nwith `chat/inputCompleted`.", - "properties": { - "id": { + "toolName": { "type": "string", - "description": "Stable request identifier" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Display message for the request as a whole" - }, - "url": { - "$ref": "#/$defs/URI", - "description": "URL the user should review or open, for URL-style elicitations" + "description": "Human-readable tool name" }, - "questions": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputQuestion" - }, - "description": "Ordered questions to ask the user" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "answers": { + "_meta": { "type": "object", - "additionalProperties": { - "$ref": "#/$defs/ChatInputAnswer" - }, - "description": "Current draft or submitted answers, keyed by question ID" - } - }, - "required": [ - "id" - ] - }, - "ChatInputTextAnswerValue": { - "type": "object", - "description": "Value captured for one answer.", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Text" - }, - "value": { - "type": "string" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputNumberAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Number" + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "value": { - "type": "number" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputBooleanAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Boolean" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "value": { - "type": "boolean" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Selected" + "toolInput": { + "type": "string", + "description": "Raw tool input" }, - "value": { - "type": "string" + "status": { + "$ref": "#/$defs/ToolCallStatus.Running" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form text entered instead of selecting an option" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedManyAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.SelectedMany" + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" }, - "value": { - "type": "array", - "items": { - "type": "string" - } + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" }, - "freeformValues": { + "content": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/ToolResultContent" }, - "description": "Free-form text entered in addition to selected options" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputAnswered": { - "type": "object", - "properties": { - "state": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputAnswerState.Draft" - }, - { - "$ref": "#/$defs/ChatInputAnswerState.Submitted" - } - ], - "description": "Answer state" - }, - "value": { - "$ref": "#/$defs/ChatInputAnswerValue", - "description": "Answer value" + "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." } }, "required": [ - "state", - "value" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "confirmed" ] }, - "ChatInputSkipped": { + "ToolCallPendingResultConfirmationState": { "type": "object", + "description": "Tool finished executing, waiting for client to approve the result.", "properties": { - "state": { - "$ref": "#/$defs/ChatInputAnswerState.Skipped", - "description": "Answer state" + "toolCallId": { + "type": "string", + "description": "Unique tool call identifier" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form reason or value captured while skipping, if any" - } - }, - "required": [ - "state" - ] - }, - "Turn": { - "type": "object", - "description": "A completed request/response cycle.", - "properties": { - "id": { + "toolName": { "type": "string", - "description": "Turn identifier" + "description": "Internal tool name (for debugging/logging)" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "displayName": { + "type": "string", + "description": "Human-readable tool name" }, - "responseParts": { - "type": "array", - "items": { - "$ref": "#/$defs/ResponsePart" - }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "state": { - "$ref": "#/$defs/TurnState", - "description": "How the turn ended" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details if state is `'error'`" - } - }, - "required": [ - "id", - "message", - "responseParts", - "usage", - "state" - ] - }, - "ActiveTurn": { - "type": "object", - "description": "An in-progress turn — the assistant is actively streaming.", - "properties": { - "id": { + "toolInput": { "type": "string", - "description": "Turn identifier" - }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "description": "Raw tool input" }, - "responseParts": { - "type": "array", - "items": { - "$ref": "#/$defs/ResponsePart" - }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" - } - }, - "required": [ - "id", - "message", - "responseParts", - "usage" - ] - }, - "Message": { - "type": "object", - "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", - "properties": { - "text": { - "type": "string", - "description": "Message text" + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" }, - "origin": { + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + }, + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + }, + "error": { "type": "object", "properties": { - "kind": { + "message": { + "type": "string" + }, + "code": { "type": "string" } }, "required": [ - "kind" + "message" ], - "description": "The origin of the message" + "description": "Error details if the tool failed" }, - "attachments": { - "type": "array", - "items": { - "$ref": "#/$defs/MessageAttachment" - }, - "description": "File/selection attachments" + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "text", - "origin" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "MessageAttachmentBase": { + "ToolCallCompletedState": { "type": "object", - "description": "Common fields shared by all {@link MessageAttachment} variants.", + "description": "Tool completed successfully or with an error.", "properties": { - "label": { + "toolCallId": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool call identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "displayKind": { + "displayName": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" + }, + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + }, + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + }, + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" + }, + "status": { + "$ref": "#/$defs/ToolCallStatus.Completed" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "label" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "SimpleMessageAttachment": { + "ToolCallCancelledState": { "type": "object", - "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", + "description": "Tool call was cancelled before execution.", "properties": { - "label": { + "toolCallId": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool call identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "displayKind": { + "displayName": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Simple", - "description": "Discriminant" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "modelRepresentation": { + "toolInput": { "type": "string", - "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." + "description": "Raw tool input" + }, + "status": { + "$ref": "#/$defs/ToolCallStatus.Cancelled" + }, + "reason": { + "$ref": "#/$defs/ToolCallCancellationReason", + "description": "Why the tool was cancelled" + }, + "reasonMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Optional message explaining the cancellation" + }, + "userSuggestion": { + "$ref": "#/$defs/Message", + "description": "What the user suggested doing instead" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "label", - "type" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "reason" ] }, - "MessageEmbeddedResourceAttachment": { + "ToolDefinition": { "type": "object", - "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", + "description": "Describes a tool available in a session, provided by either the server or the active client.", "properties": { - "label": { + "name": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "title": { + "type": "string", + "description": "Human-readable display name" + }, + "description": { + "type": "string", + "description": "Description of what the tool does" + }, + "inputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } + }, + "required": [ + "type" + ], + "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." + }, + "outputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } + }, + "required": [ + "type" + ], + "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "annotations": { + "$ref": "#/$defs/ToolAnnotations", + "description": "Behavioral hints about the tool. All properties are advisory." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", - "description": "Discriminant" - }, - "data": { - "type": "string", - "description": "Base64-encoded binary data" - }, - "contentType": { - "type": "string", - "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" - }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." + "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." } }, "required": [ - "label", - "type", - "data", - "contentType" + "name" ] }, - "MessageResourceAttachment": { + "ToolAnnotations": { "type": "object", - "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", + "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." - }, - "displayKind": { + "title": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Content URI" + "description": "Alternate human-readable title" }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" + "readOnlyHint": { + "type": "boolean", + "description": "Tool does not modify its environment (default: false)" }, - "contentType": { - "type": "string", - "description": "Content MIME type" + "destructiveHint": { + "type": "boolean", + "description": "Tool may perform destructive updates (default: true)" }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Resource", - "description": "Discriminant" + "idempotentHint": { + "type": "boolean", + "description": "Repeated calls with the same arguments have no additional effect (default: false)" }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." + "openWorldHint": { + "type": "boolean", + "description": "Tool may interact with external entities (default: true)" } - }, - "required": [ - "label", - "uri", - "type" - ] + } }, - "MessageAnnotationsAttachment": { + "ToolResultTextContent": { "type": "object", - "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", + "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." - }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Annotations", - "description": "Discriminant" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." + "$ref": "#/$defs/ToolResultContentType.Text" }, - "annotationIds": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." + "text": { + "type": "string", + "description": "The text content" } }, "required": [ - "label", "type", - "resource" + "text" ] }, - "MarkdownResponsePart": { + "ToolResultEmbeddedResourceContent": { "type": "object", + "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Markdown", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" }, - "id": { + "data": { "type": "string", - "description": "Part identifier, used by `chat/delta` to target this part for content appends" + "description": "Base64-encoded data" }, - "content": { + "contentType": { "type": "string", - "description": "Markdown content" + "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" } }, "required": [ - "kind", - "id", - "content" + "type", + "data", + "contentType" ] }, - "ResourceReponsePart": { + "ToolResultResourceContent": { "type": "object", - "description": "A content part that's a reference to large content stored outside the state tree.", + "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", "properties": { "uri": { "$ref": "#/$defs/URI", @@ -3959,819 +3725,874 @@ "type": "string", "description": "Content MIME type" }, - "kind": { - "$ref": "#/$defs/ResponsePartKind.ContentRef", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Resource" } }, "required": [ "uri", - "kind" + "type" ] }, - "ToolCallResponsePart": { + "ToolResultFileEditContent": { "type": "object", - "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", + "description": "Describes a file modification performed by a tool.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.ToolCall", - "description": "Discriminant" + "before": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state before the edit. Absent for file creations or for in-place file edits." }, - "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "Full tool call lifecycle state" + "after": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state after the edit. Absent for file deletions." + }, + "diff": { + "type": "object", + "properties": { + "added": { + "type": "number" + }, + "removed": { + "type": "number" + } + }, + "description": "Optional diff display metadata" + }, + "type": { + "$ref": "#/$defs/ToolResultContentType.FileEdit" } }, "required": [ - "kind", - "toolCall" + "type" ] }, - "ReasoningResponsePart": { + "ToolResultTerminalContent": { "type": "object", - "description": "Reasoning/thinking content from the model.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Reasoning", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Terminal" }, - "id": { - "type": "string", - "description": "Part identifier, used by `chat/reasoning` to target this part for content appends" + "resource": { + "$ref": "#/$defs/URI", + "description": "Terminal URI (subscribable for full terminal state)" }, - "content": { + "title": { "type": "string", - "description": "Accumulated reasoning text" + "description": "Display title for the terminal content" } }, "required": [ - "kind", - "id", - "content" + "type", + "resource", + "title" ] }, - "SystemNotificationResponsePart": { + "ToolResultSubagentContent": { "type": "object", - "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", + "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.SystemNotification", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Subagent" + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "Subagent session URI (subscribable for full session state)" + }, + "title": { + "type": "string", + "description": "Display title for the subagent" }, - "content": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "The text of the system notification" + "agentName": { + "type": "string", + "description": "Internal agent name" + }, + "description": { + "type": "string", + "description": "Human-readable description of the subagent's task" } }, "required": [ - "kind", - "content" + "type", + "resource", + "title" ] }, - "ConfirmationOption": { + "CustomizationBase": { "type": "object", - "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", + "description": "Fields shared by every customization variant.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the option, returned in the confirmed action" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "label": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Human-readable label displayed to the user" + "description": "Human-readable name." }, - "kind": { - "$ref": "#/$defs/ConfirmationOptionKind", - "description": "Whether this option represents an approval or denial" + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "group": { - "type": "number", - "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." } }, "required": [ "id", - "label", - "kind" + "uri", + "name" ] }, - "ToolCallClientContributor": { + "CustomizationLoadingState": { "type": "object", + "description": "Container is being loaded by the host.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.Client" - }, - "clientId": { - "type": "string", - "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `chat/toolCallComplete` with the result." + "$ref": "#/$defs/CustomizationLoadStatus.Loading" } }, "required": [ - "kind", - "clientId" + "kind" ] }, - "ToolCallMcpContributor": { + "CustomizationLoadedState": { "type": "object", + "description": "Container loaded successfully.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.MCP" - }, - "customizationId": { - "type": "string", - "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." + "$ref": "#/$defs/CustomizationLoadStatus.Loaded" } }, "required": [ - "kind", - "customizationId" + "kind" ] }, - "ToolCallBase": { + "CustomizationDegradedState": { "type": "object", - "description": "Metadata common to all tool call states.", + "description": "Container partially loaded but has warnings.", "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Degraded" }, - "displayName": { + "message": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "description": "Human-readable description of the warning." } }, "required": [ - "toolCallId", - "toolName", - "displayName" + "kind", + "message" ] }, - "ToolCallParameterFields": { + "CustomizationErrorState": { "type": "object", - "description": "Properties available once tool call parameters are fully received.", + "description": "Container failed to load.", "properties": { - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Error" }, - "toolInput": { + "message": { "type": "string", - "description": "Raw tool input" + "description": "Human-readable error message." } }, "required": [ - "invocationMessage" + "kind", + "message" ] }, - "ToolCallResult": { + "ContainerCustomizationBase": { "type": "object", - "description": "Tool execution result details, available after execution completes.", + "description": "Fields shared by container customizations.", "properties": { - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "content": { + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } - }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" - } - }, - "required": [ - "success", - "pastTenseMessage" - ] - }, - "ToolCallStreamingState": { - "type": "object", - "description": "LM is streaming the tool call parameters.", - "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "displayName": { + "clientId": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Streaming" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "partialInput": { - "type": "string", - "description": "Partial parameters accumulated so far" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Progress message shown while parameters are streaming" + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/ChildCustomization" + }, + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "status" + "id", + "uri", + "name", + "enabled" ] - }, - "ToolCallPendingConfirmationState": { - "type": "object", - "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", - "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" - }, - "displayName": { - "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" - }, - "toolInput": { + }, + "PluginCustomization": { + "type": "object", + "description": "An [Open Plugins](https://open-plugins.com/) plugin.", + "properties": { + "id": { "type": "string", - "description": "Raw tool input" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "confirmationTitle": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" + "name": { + "type": "string", + "description": "Human-readable name." }, - "edits": { - "type": "object", - "properties": { - "items": { - "type": "string" - } + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" }, - "required": [ - "items" - ], - "description": "File edits that this tool call will perform, for preview before confirmation" + "description": "Icons for UI display." }, - "editable": { + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "enabled": { "type": "boolean", - "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" + "description": "Whether this container is currently enabled." }, - "options": { + "clientId": { + "type": "string", + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + }, + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + }, + "children": { "type": "array", "items": { - "$ref": "#/$defs/ConfirmationOption" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallRunningState": { + "ClientPluginCustomization": { "type": "object", - "description": "Tool is actively executing.", + "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Running" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "content": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" + }, + "nonce": { + "type": "string", + "description": "Opaque version token used by the host to detect changes." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallPendingResultConfirmationState": { + "DirectoryCustomization": { "type": "object", - "description": "Tool finished executing, waiting for client to approve the result.", + "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "content": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" - }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." - }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } + "$ref": "#/$defs/ChildCustomization" }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" + "type": { + "$ref": "#/$defs/CustomizationType.Directory" }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "contents": { + "$ref": "#/$defs/ChildCustomizationType", + "description": "Which child customization type this directory holds." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "writable": { + "type": "boolean", + "description": "Whether clients may write into this directory." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "enabled", + "type", + "contents", + "writable" ] }, - "ToolCallCompletedState": { + "AgentCustomization": { "type": "object", - "description": "Tool completed successfully or with an error.", + "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Agent" + }, + "description": { + "type": "string", + "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" - }, - "toolInput": { + "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." + } + }, + "required": [ + "id", + "uri", + "name", + "type" + ] + }, + "SkillCustomization": { + "type": "object", + "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", + "properties": { + "id": { "type": "string", - "description": "Raw tool input" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "name": { + "type": "string", + "description": "Human-readable name." }, - "content": { + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } - }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Completed" + "type": { + "$ref": "#/$defs/CustomizationType.Skill" }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": { + "type": "string", + "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "disableModelInvocation": { + "type": "boolean", + "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "type" ] }, - "ToolCallCancelledState": { + "PromptCustomization": { "type": "object", - "description": "Tool call was cancelled before execution.", + "description": "A prompt contributed by a plugin or directory.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "type": { + "$ref": "#/$defs/CustomizationType.Prompt" }, - "toolInput": { + "description": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Cancelled" - }, - "reason": { - "$ref": "#/$defs/ToolCallCancellationReason", - "description": "Why the tool was cancelled" - }, - "reasonMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Optional message explaining the cancellation" - }, - "userSuggestion": { - "$ref": "#/$defs/Message", - "description": "What the user suggested doing instead" - }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "description": "Short description of what the prompt does." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "reason" + "id", + "uri", + "name", + "type" ] }, - "ToolResultTextContent": { + "RuleCustomization": { "type": "object", - "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", + "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", "properties": { + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + }, + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, "type": { - "$ref": "#/$defs/ToolResultContentType.Text" + "$ref": "#/$defs/CustomizationType.Rule" }, - "text": { + "description": { "type": "string", - "description": "The text content" + "description": "Description of what the rule enforces." + }, + "alwaysApply": { + "type": "boolean", + "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." + }, + "globs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." } }, "required": [ - "type", - "text" + "id", + "uri", + "name", + "type" ] }, - "ToolResultEmbeddedResourceContent": { + "HookCustomization": { "type": "object", - "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", + "description": "A hook manifest contributed by a plugin or directory.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" - }, - "data": { + "id": { "type": "string", - "description": "Base64-encoded data" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "contentType": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Hook" } }, "required": [ - "type", - "data", - "contentType" + "id", + "uri", + "name", + "type" ] }, - "ToolResultResourceContent": { + "McpServerCustomization": { "type": "object", - "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", + "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", "properties": { + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + }, "uri": { "$ref": "#/$defs/URI", - "description": "Content URI" - }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "contentType": { + "name": { "type": "string", - "description": "Content MIME type" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, "type": { - "$ref": "#/$defs/ToolResultContentType.Resource" + "$ref": "#/$defs/CustomizationType.McpServer" + }, + "enabled": { + "type": "boolean", + "description": "Whether this MCP server is currently enabled." + }, + "state": { + "$ref": "#/$defs/McpServerState", + "description": "Current lifecycle state of the MCP server." + }, + "channel": { + "$ref": "#/$defs/URI", + "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + }, + "mcpApp": { + "$ref": "#/$defs/McpServerCustomizationApps", + "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." } }, "required": [ + "id", "uri", - "type" + "name", + "type", + "enabled", + "state" ] }, - "ToolResultFileEditContent": { + "McpServerCustomizationApps": { "type": "object", - "description": "Describes a file modification performed by a tool.", + "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", "properties": { - "before": { + "capabilities": { + "$ref": "#/$defs/AhpMcpUiHostCapabilities", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." + } + }, + "required": [ + "capabilities" + ] + }, + "AhpMcpUiHostCapabilities": { + "type": "object", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", + "properties": { + "serverTools": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state before the edit. Absent for file creations or for in-place file edits." + "description": "Producer proxies the MCP `tools/*` methods to the upstream server." }, - "after": { + "serverResources": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state after the edit. Absent for file deletions." + "description": "Producer proxies the MCP `resources/*` methods to the upstream server." }, - "diff": { + "logging": { + "type": "object", + "additionalProperties": {}, + "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + }, + "sampling": { "type": "object", "properties": { - "added": { - "type": "number" - }, - "removed": { - "type": "number" + "tools": { + "type": "string" } }, - "description": "Optional diff display metadata" - }, - "type": { - "$ref": "#/$defs/ToolResultContentType.FileEdit" + "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." + } + } + }, + "McpServerStartingState": { + "type": "object", + "description": "Server is registered with the host but has not yet started.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Starting" } }, "required": [ - "type" + "kind" ] }, - "ToolResultTerminalContent": { + "McpServerReadyState": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "Server is running and serving requests.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Terminal" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Terminal URI (subscribable for full terminal state)" - }, - "title": { - "type": "string", - "description": "Display title for the terminal content" + "kind": { + "$ref": "#/$defs/McpServerStatus.Ready" } }, "required": [ - "type", - "resource", - "title" + "kind" ] }, - "ToolResultSubagentContent": { + "McpServerAuthRequiredState": { "type": "object", - "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", + "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Subagent" + "kind": { + "$ref": "#/$defs/McpServerStatus.AuthRequired" }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Subagent session URI (subscribable for full session state)" + "reason": { + "$ref": "#/$defs/McpAuthRequiredReason", + "description": "Why authentication is required." }, - "title": { - "type": "string", - "description": "Display title for the subagent" + "resource": { + "$ref": "#/$defs/ProtectedResourceMetadata", + "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." }, - "agentName": { - "type": "string", - "description": "Internal agent name" + "requiredScopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." }, "description": { "type": "string", - "description": "Human-readable description of the subagent's task" + "description": "Human-readable hint, typically from the OAuth `error_description`." } }, "required": [ - "type", - "resource", - "title" + "kind", + "reason", + "resource" + ] + }, + "McpServerErrorState": { + "type": "object", + "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Error" + }, + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details." + } + }, + "required": [ + "kind", + "error" + ] + }, + "McpServerStoppedState": { + "type": "object", + "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Stopped" + } + }, + "required": [ + "kind" ] }, "TerminalInfo": { @@ -5396,6 +5217,29 @@ "config" ] }, + "ToolCallActionBase": { + "type": "object", + "description": "Base interface for all tool-call-scoped actions, carrying the common turn\nand tool call identifiers. The owning session URI is identified by the\nenclosing {@link ActionEnvelope}'s `channel` field.", + "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + } + }, + "required": [ + "turnId", + "toolCallId" + ] + }, "SessionReadyAction": { "type": "object", "description": "Session backend initialized successfully.", @@ -5425,496 +5269,528 @@ "error" ] }, - "SessionChatAddedAction": { + "SessionTurnStartedAction": { "type": "object", - "description": "A chat was added to this session's catalog. Upsert semantics: if a chat\nwith the same `summary.resource` already exists, the existing entry is\nreplaced.\n\nMirrors the root-channel `root/sessionAdded` notification.", + "description": "A new message has been sent to the agent, and a new turn starts.\n\nA client is only allowed to send {@link MessageKind.User} messages.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionChatAdded" + "$ref": "#/$defs/ActionType.SessionTurnStarted" }, - "summary": { - "$ref": "#/$defs/ChatSummary", - "description": "The full summary of the newly added (or upserted) chat." + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "message": { + "$ref": "#/$defs/Message", + "description": "The new message" + }, + "queuedMessageId": { + "type": "string", + "description": "If this turn was auto-started from a queued message, the ID of that message" } }, "required": [ "type", - "summary" + "turnId", + "message" ] }, - "SessionChatRemovedAction": { + "SessionDeltaAction": { "type": "object", - "description": "A chat was removed from this session's catalog. No-op when no entry matches.\n\nMirrors the root-channel `root/sessionRemoved` notification.", + "description": "Streaming text chunk from the assistant, appended to a specific response part.\n\nThe server MUST first emit a `session/responsePart` to create the target\npart (markdown or reasoning), then use this action to append text to it.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionChatRemoved" + "$ref": "#/$defs/ActionType.SessionDelta" }, - "chat": { - "$ref": "#/$defs/URI", - "description": "The URI of the chat to remove." + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "partId": { + "type": "string", + "description": "Identifier of the response part to append to" + }, + "content": { + "type": "string", + "description": "Text chunk" } }, "required": [ "type", - "chat" + "turnId", + "partId", + "content" ] }, - "SessionChatUpdatedAction": { + "SessionResponsePartAction": { "type": "object", - "description": "One existing chat's summary fields changed.\n\nPartial-update semantics: only fields present in `changes` are written;\nomitted fields are preserved. Identity fields (`resource`) MUST NOT be\ncarried in `changes`. No-op when no entry with `chat` exists — clients\nSHOULD then wait for a {@link SessionChatAddedAction | `session/chatAdded`}.\n\nMirrors the root-channel `root/sessionSummaryChanged` notification.", + "description": "Structured content appended to the response.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionChatUpdated" + "$ref": "#/$defs/ActionType.SessionResponsePart" }, - "chat": { - "$ref": "#/$defs/URI", - "description": "The URI of the chat whose summary changed." + "turnId": { + "type": "string", + "description": "Turn identifier" }, - "changes": { - "type": "object", - "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { - "type": "string", - "description": "Human-readable description of what the chat is currently doing" - }, - "modifiedAt": { - "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nSee {@link ChatState.workingDirectory} for usage notes." - } - }, - "description": "Mutable summary fields that changed; omitted fields are unchanged.\n\nIdentity fields (`resource`) never change and MUST be omitted by\nsenders; receivers SHOULD ignore them if present." + "part": { + "$ref": "#/$defs/ResponsePart", + "description": "Response part (markdown or content ref)" } }, "required": [ "type", - "chat", - "changes" + "turnId", + "part" ] }, - "SessionDefaultChatChangedAction": { + "SessionToolCallStartAction": { "type": "object", - "description": "The default chat input-routing hint for this session changed.", + "description": "A tool call begins — parameters are streaming from the LM.\n\nThe server sets {@link ToolCallContributor | `contributor`} to identify\nthe origin of the tool. For client-provided tools, the named client is\nresponsible for executing the tool once it reaches the `running` state\nand dispatching `session/toolCallComplete`. For MCP-served tools, the\nserver executes the call against the named `McpServerCustomization`.", "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionDefaultChatChanged" + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." }, - "defaultChat": { - "$ref": "#/$defs/URI", - "description": "New default chat URI, or `undefined` to clear the hint." - } - }, - "required": [ - "type" - ] - }, - "SessionTitleChangedAction": { - "type": "object", - "description": "Session title updated. Fired by the server when the title is auto-generated\nfrom conversation, or dispatched by a client to rename a session.", - "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionTitleChanged" + "$ref": "#/$defs/ActionType.SessionToolCallStart" }, - "title": { + "toolName": { "type": "string", - "description": "New title" - } - }, - "required": [ - "type", - "title" - ] - }, - "SessionModelChangedAction": { - "type": "object", - "description": "Model changed for this session.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionModelChanged" + "description": "Internal tool name (for debugging/logging)" }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "New model selection" + "displayName": { + "type": "string", + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called. Absent for\nserver-side tools that are not contributed by a client or MCP server." } }, "required": [ + "turnId", + "toolCallId", "type", - "model" + "toolName", + "displayName" ] }, - "SessionAgentChangedAction": { + "SessionToolCallDeltaAction": { "type": "object", - "description": "Custom agent selection changed for this session.\n\nOmitting `agent` (or setting it to `undefined`) clears the selection and\nresets the session to no selected custom agent (provider default behavior).\n\nWhen a turn is currently active, the server MUST defer the change until\nthe active turn completes, then apply it for the next turn (same rule as\n{@link SessionModelChangedAction | `session/modelChanged`}).", + "description": "Streaming partial parameters for a tool call.", "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionAgentChanged" + "turnId": { + "type": "string", + "description": "Turn identifier" }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "New agent selection, or `undefined` to clear the selection and reset the\nsession to no selected custom agent." - } - }, - "required": [ - "type" - ] - }, - "SessionIsReadChangedAction": { - "type": "object", - "description": "The read state of the session changed.\n\nDispatched by a client to mark a session as read (e.g. after viewing it)\nor unread (e.g. after new activity since the client last looked at it).", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionIsReadChanged" + "toolCallId": { + "type": "string", + "description": "Tool call identifier" }, - "isRead": { - "type": "boolean", - "description": "Whether the session has been read" - } - }, - "required": [ - "type", - "isRead" - ] - }, - "SessionIsArchivedChangedAction": { - "type": "object", - "description": "The archived state of the session changed.\n\nDispatched by a client to archive a session (e.g. the task is\ncomplete) or to unarchive it.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionIsArchivedChanged" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." }, - "isArchived": { - "type": "boolean", - "description": "Whether the session is archived" - } - }, - "required": [ - "type", - "isArchived" - ] - }, - "SessionActivityChangedAction": { - "type": "object", - "description": "The activity description of the session changed.\n\nDispatched by the server to indicate what the session is currently doing\n(e.g. running a tool, thinking). Clear activity by setting it to `undefined`.", - "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionActivityChanged" + "$ref": "#/$defs/ActionType.SessionToolCallDelta" }, - "activity": { + "content": { "type": "string", - "description": "Human-readable description of current activity, or `undefined` to clear" + "description": "Partial parameter content to append" + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Updated progress message" } }, "required": [ + "turnId", + "toolCallId", "type", - "activity" + "content" ] }, - "SessionChangesetsChangedAction": { + "SessionToolCallReadyAction": { "type": "object", - "description": "The {@link Changeset | catalogue of changesets} the agent host\nadvertises for this session changed. Replaces\n{@link SessionState.changesets | `state.changesets`} entirely\n(full-replacement semantics) — set to `undefined` to clear the\ncatalogue.\n\nProducers dispatch this whenever entries are added or removed. The\nfan-out happens through this action so observers see catalogue\nmutations in the same {@link ChangesetAction | per-changeset} action\nstream they already follow for file-level updates.", + "description": "Tool call parameters are complete, or a running tool requires re-confirmation.\n\nWhen dispatched for a `streaming` tool call, transitions to `pending-confirmation`\nor directly to `running` if `confirmed` is set.\n\nWhen dispatched for a `running` tool call (e.g. mid-execution permission needed),\ntransitions back to `pending-confirmation`. The `invocationMessage` and `_meta`\nSHOULD be updated to describe the specific confirmation needed. Clients use the\nstandard `session/toolCallConfirmed` flow to approve or deny.\n\nFor client-provided tools, the server typically sets `confirmed` to\n`'not-needed'` so the tool transitions directly to `running`, where the\nowning client can begin execution immediately.", "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, "type": { - "$ref": "#/$defs/ActionType.SessionChangesetsChanged" + "$ref": "#/$defs/ActionType.SessionToolCallReady" }, - "changesets": { + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do or what confirmation is needed" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "confirmationTitle": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" + }, + "edits": { + "type": "object", + "properties": { + "items": { + "type": "string" + } + }, + "required": [ + "items" + ], + "description": "File edits that this tool call will perform, for preview before confirmation" + }, + "editable": { + "type": "boolean", + "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "If set, the tool was auto-confirmed and transitions directly to `running`" + }, + "options": { "type": "array", "items": { - "$ref": "#/$defs/Changeset" + "$ref": "#/$defs/ConfirmationOption" }, - "description": "New catalogue, or `undefined` to clear it" + "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." } }, "required": [ + "turnId", + "toolCallId", "type", - "changesets" + "invocationMessage" ] }, - "SessionServerToolsChangedAction": { + "SessionToolCallApprovedAction": { "type": "object", - "description": "Server tools for this session have changed.\n\nFull-replacement semantics: the `tools` array replaces the previous `serverTools` entirely.", + "description": "Client approves a pending tool call. The tool transitions to `running`.", "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, "type": { - "$ref": "#/$defs/ActionType.SessionServerToolsChanged" + "$ref": "#/$defs/ActionType.SessionToolCallConfirmed" }, - "tools": { - "type": "array", - "items": { - "$ref": "#/$defs/ToolDefinition" - }, - "description": "Updated server tools list (full replacement)" + "approved": { + "description": "The tool call was approved" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed" + }, + "editedToolInput": { + "type": "string", + "description": "Edited tool input parameters, if the client modified them before confirming" + }, + "selectedOptionId": { + "type": "string", + "description": "ID of the selected confirmation option, if the server provided options" } }, "required": [ + "turnId", + "toolCallId", "type", - "tools" + "approved", + "confirmed" ] }, - "SessionActiveClientChangedAction": { + "SessionToolCallDeniedAction": { "type": "object", - "description": "The active client for this session has changed.\n\nA client dispatches this action with its own `SessionActiveClient` to claim\nthe active role, or with `null` to release it. The server SHOULD reject if\nanother client is already active. The server SHOULD automatically dispatch\nthis action with `activeClient: null` when the active client disconnects.", + "description": "Client denies a pending tool call. The tool transitions to `cancelled`.\n\nFor client-provided tools, the owning client MUST dispatch this if it does\nnot recognize the tool or cannot execute it.", "properties": { + "turnId": { + "type": "string", + "description": "Turn identifier" + }, + "toolCallId": { + "type": "string", + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, "type": { - "$ref": "#/$defs/ActionType.SessionActiveClientChanged" + "$ref": "#/$defs/ActionType.SessionToolCallConfirmed" }, - "activeClient": { + "approved": { + "description": "The tool call was denied" + }, + "reason": { "oneOf": [ { - "$ref": "#/$defs/SessionActiveClient" + "$ref": "#/$defs/ToolCallCancellationReason.Denied" }, - {} + { + "$ref": "#/$defs/ToolCallCancellationReason.Skipped" + } ], - "description": "The new active client, or `null` to unset" - } - }, - "required": [ - "type", - "activeClient" - ] - }, - "SessionActiveClientToolsChangedAction": { - "type": "object", - "description": "The active client's tool list has changed.\n\nFull-replacement semantics: the `tools` array replaces the active client's\nprevious tools entirely. The server SHOULD reject if the dispatching client\nis not the current active client.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionActiveClientToolsChanged" + "description": "Why the tool was cancelled" }, - "tools": { - "type": "array", - "items": { - "$ref": "#/$defs/ToolDefinition" - }, - "description": "Updated client tools list (full replacement)" - } - }, - "required": [ - "type", - "tools" - ] - }, - "SessionCustomizationsChangedAction": { - "type": "object", - "description": "The session's customizations have changed.\n\nFull-replacement semantics: the `customizations` array replaces the\nprevious `customizations` entirely.", - "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionCustomizationsChanged" + "userSuggestion": { + "$ref": "#/$defs/Message", + "description": "What the user suggested doing instead" }, - "customizations": { - "type": "array", - "items": { - "$ref": "#/$defs/Customization" - }, - "description": "Updated customization list (full replacement)." + "reasonMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Optional explanation for the denial" + }, + "selectedOptionId": { + "type": "string", + "description": "ID of the selected confirmation option, if the server provided options" } }, "required": [ + "turnId", + "toolCallId", "type", - "customizations" + "approved", + "reason" ] }, - "SessionCustomizationToggledAction": { + "SessionToolCallCompleteAction": { "type": "object", - "description": "A client toggled a container customization on or off.\n\nTargets a top-level container (plugin or directory) by `id`. Only\ncontainers have an `enabled` flag; children are always active when\ntheir container is enabled. Is a no-op when no matching container is\nfound.", + "description": "Tool execution finished. Transitions to `completed` or `pending-result-confirmation`\nif `requiresResultConfirmation` is `true`.\n\nFor client-provided tools (where `toolClientId` is set on the tool call state),\nthe owning client dispatches this action with the execution result. The server\nSHOULD reject this action if the dispatching client does not match `toolClientId`.\n\nServers waiting on a client tool call MAY time out after a reasonable duration\nif the implementing client disconnects or becomes unresponsive, and dispatch\nthis action with `result.success = false` and an appropriate error.", "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionCustomizationToggled" + "turnId": { + "type": "string", + "description": "Turn identifier" }, - "id": { + "toolCallId": { "type": "string", - "description": "The id of the container to toggle." + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." }, - "enabled": { - "type": "boolean", - "description": "Whether to enable or disable the container." - } - }, - "required": [ - "type", - "id", - "enabled" - ] - }, - "SessionCustomizationUpdatedAction": { - "type": "object", - "description": "Upserts a top-level customization (plugin or directory).\n\nThe reducer locates the existing entry by `customization.id`:\n\n- If found, the entry is replaced entirely with `customization`,\n including its `children` array. To preserve existing children, the\n host must include them on the payload.\n- If not found, the entry is appended.", - "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionCustomizationUpdated" + "$ref": "#/$defs/ActionType.SessionToolCallComplete" }, - "customization": { - "$ref": "#/$defs/Customization", - "description": "The customization to upsert (matched by `customization.id`)." + "result": { + "$ref": "#/$defs/ToolCallResult", + "description": "Execution result" + }, + "requiresResultConfirmation": { + "type": "boolean", + "description": "If true, the result requires client approval before finalizing" } }, "required": [ + "turnId", + "toolCallId", "type", - "customization" + "result" ] }, - "SessionCustomizationRemovedAction": { + "SessionToolCallResultConfirmedAction": { "type": "object", - "description": "Removes a customization by id.\n\nSearches every container and its children for the entry. If the entry\nis a container, its children are removed with it. Is a no-op when no\nmatching id is found.", + "description": "Client approves or denies a tool's result.\n\nIf `approved` is `false`, the tool transitions to `cancelled` with reason `result-denied`.", "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionCustomizationRemoved" + "turnId": { + "type": "string", + "description": "Turn identifier" }, - "id": { + "toolCallId": { "type": "string", - "description": "The id of the customization to remove." + "description": "Tool call identifier" + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + }, + "type": { + "$ref": "#/$defs/ActionType.SessionToolCallResultConfirmed" + }, + "approved": { + "type": "boolean", + "description": "Whether the result was approved" } }, "required": [ + "turnId", + "toolCallId", "type", - "id" + "approved" ] }, - "SessionMcpServerStateChangedAction": { + "SessionToolCallContentChangedAction": { "type": "object", - "description": "Updates the runtime fields of an existing\n{@link McpServerCustomization} — narrow alternative to\n{@link SessionCustomizationUpdatedAction} for the high-frequency\n`starting` ↔ `ready` ↔ `authRequired` transitions.\n\nLocates the target entry by `id`, searching both the top-level\ncustomization list and the `children` array of every container.\nReplaces the entry's {@link McpServerCustomization.state | `state`}\nand {@link McpServerCustomization.channel | `channel`}\n(full-replacement semantics: omit `channel` to clear an existing\nchannel URI). Other fields of the customization are preserved.\n\nIs a no-op when no matching `McpServerCustomization` is found. To\nupdate any other field (name, icons, `mcpApp` capabilities, etc.) use\n{@link SessionCustomizationUpdatedAction} instead.\n\nWhen the transition is to {@link McpServerStatus.AuthRequired}\nbecause of a request issued mid-turn, the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session — see\n{@link McpServerAuthRequiredState} for the rationale.", + "description": "Partial content produced while a tool is still executing.\n\nReplaces the `content` array on the running tool call state. Clients can\nuse this to display live feedback (e.g. a terminal reference) before the\ntool completes.\n\nFor client-provided tools (where `toolClientId` is set on the tool call state),\nthe owning client dispatches this action to stream intermediate content while\nexecuting. The server SHOULD reject this action if the dispatching client does\nnot match `toolClientId`.", "properties": { - "type": { - "$ref": "#/$defs/ActionType.SessionMcpServerStateChanged" + "turnId": { + "type": "string", + "description": "Turn identifier" }, - "id": { + "toolCallId": { "type": "string", - "description": "The id of the {@link McpServerCustomization} to update." + "description": "Tool call identifier" }, - "state": { - "$ref": "#/$defs/McpServerState", - "description": "The new lifecycle state." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." }, - "channel": { - "$ref": "#/$defs/URI", - "description": "Updated `mcp://` side-channel URI. Full-replacement: omit to clear\nan existing channel (typical when leaving\n{@link McpServerStatus.Ready | `Ready`})." + "type": { + "$ref": "#/$defs/ActionType.SessionToolCallContentChanged" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "The current partial content for the running tool call" } }, "required": [ + "turnId", + "toolCallId", "type", - "id", - "state" + "content" ] }, - "SessionConfigChangedAction": { + "SessionTurnCompleteAction": { "type": "object", - "description": "Client changed a mutable config value mid-session.\n\nOnly properties with `sessionMutable: true` in the config schema may be\nchanged. The server validates and broadcasts the action; the reducer merges\nthe new values into `state.config.values`.", + "description": "Turn finished — the assistant is idle.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionConfigChanged" - }, - "config": { - "type": "object", - "additionalProperties": {}, - "description": "Updated config values" - }, - "replace": { - "type": "boolean", - "description": "When `true`, replaces all config values instead of merging" + "$ref": "#/$defs/ActionType.SessionTurnComplete" + }, + "turnId": { + "type": "string", + "description": "Turn identifier" } }, "required": [ "type", - "config" + "turnId" ] }, - "SessionMetaChangedAction": { + "SessionTurnCancelledAction": { "type": "object", - "description": "The session's `_meta` side-channel changed. Replaces `state._meta`\nentirely (full-replacement semantics). Producers SHOULD merge any\nkeys they wish to preserve into the new value before dispatching.", + "description": "Turn was aborted; server stops processing.", "properties": { "type": { - "$ref": "#/$defs/ActionType.SessionMetaChanged" + "$ref": "#/$defs/ActionType.SessionTurnCancelled" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "New `_meta` payload, or `undefined` to clear it" + "turnId": { + "type": "string", + "description": "Turn identifier" } }, "required": [ "type", - "_meta" + "turnId" ] }, - "ToolCallActionBase": { + "SessionErrorAction": { "type": "object", - "description": "Base interface for all tool-call-scoped actions, carrying the common turn\nand tool call identifiers. The owning chat URI is identified by the\nenclosing {@link ActionEnvelope}'s `channel` field.", + "description": "Error during turn processing.", "properties": { + "type": { + "$ref": "#/$defs/ActionType.SessionError" + }, "turnId": { "type": "string", "description": "Turn identifier" }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details" } }, "required": [ + "type", "turnId", - "toolCallId" + "error" ] }, - "ChatTurnStartedAction": { + "SessionTitleChangedAction": { "type": "object", - "description": "A new message has been sent to the agent, and a new turn starts.\n\nA client is only allowed to send {@link MessageKind.User} messages.", + "description": "Session title updated. Fired by the server when the title is auto-generated\nfrom conversation, or dispatched by a client to rename a session.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.SessionTitleChanged" + }, + "title": { + "type": "string", + "description": "New title" + } + }, + "required": [ + "type", + "title" + ] + }, + "SessionUsageAction": { + "type": "object", + "description": "Token usage report for a turn.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatTurnStarted" + "$ref": "#/$defs/ActionType.SessionUsage" }, "turnId": { "type": "string", "description": "Turn identifier" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The new message" - }, - "queuedMessageId": { - "type": "string", - "description": "If this turn was auto-started from a queued message, the ID of that message" + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage data" } }, "required": [ "type", "turnId", - "message" + "usage" ] }, - "ChatDeltaAction": { + "SessionReasoningAction": { "type": "object", - "description": "Streaming text chunk from the assistant, appended to a specific response part.\n\nThe server MUST first emit a `chat/responsePart` to create the target\npart (markdown or reasoning), then use this action to append text to it.", + "description": "Reasoning/thinking text from the model, appended to a specific reasoning response part.\n\nThe server MUST first emit a `session/responsePart` to create the target\nreasoning part, then use this action to append text to it.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatDelta" + "$ref": "#/$defs/ActionType.SessionReasoning" }, "turnId": { "type": "string", @@ -5922,11 +5798,11 @@ }, "partId": { "type": "string", - "description": "Identifier of the response part to append to" + "description": "Identifier of the reasoning response part to append to" }, "content": { "type": "string", - "description": "Text chunk" + "description": "Reasoning text chunk" } }, "required": [ @@ -5936,485 +5812,320 @@ "content" ] }, - "ChatResponsePartAction": { + "SessionModelChangedAction": { "type": "object", - "description": "Structured content appended to the response.", + "description": "Model changed for this session.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatResponsePart" - }, - "turnId": { - "type": "string", - "description": "Turn identifier" + "$ref": "#/$defs/ActionType.SessionModelChanged" }, - "part": { - "$ref": "#/$defs/ResponsePart", - "description": "Response part (markdown or content ref)" + "model": { + "$ref": "#/$defs/ModelSelection", + "description": "New model selection" } }, "required": [ "type", - "turnId", - "part" + "model" ] }, - "ChatToolCallStartAction": { + "SessionAgentChangedAction": { "type": "object", - "description": "A tool call begins — parameters are streaming from the LM.\n\nThe server sets {@link ToolCallContributor | `contributor`} to identify\nthe origin of the tool. For client-provided tools, the named client is\nresponsible for executing the tool once it reaches the `running` state\nand dispatching `chat/toolCallComplete`. For MCP-served tools, the\nserver executes the call against the named `McpServerCustomization`.", + "description": "Custom agent selection changed for this session.\n\nOmitting `agent` (or setting it to `undefined`) clears the selection and\nresets the session to no selected custom agent (provider default behavior).\n\nWhen a turn is currently active, the server MUST defer the change until\nthe active turn completes, then apply it for the next turn (same rule as\n{@link SessionModelChangedAction | `session/modelChanged`}).", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallStart" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "$ref": "#/$defs/ActionType.SessionAgentChanged" }, - "displayName": { - "type": "string", - "description": "Human-readable tool name" + "agent": { + "$ref": "#/$defs/AgentSelection", + "description": "New agent selection, or `undefined` to clear the selection and reset the\nsession to no selected custom agent." + } + }, + "required": [ + "type" + ] + }, + "SessionIsReadChangedAction": { + "type": "object", + "description": "The read state of the session changed.\n\nDispatched by a client to mark a session as read (e.g. after viewing it)\nor unread (e.g. after new activity since the client last looked at it).", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.SessionIsReadChanged" }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called. Absent for\nserver-side tools that are not contributed by a client or MCP server." + "isRead": { + "type": "boolean", + "description": "Whether the session has been read" } }, "required": [ - "turnId", - "toolCallId", "type", - "toolName", - "displayName" + "isRead" ] }, - "ChatToolCallDeltaAction": { + "SessionIsArchivedChangedAction": { "type": "object", - "description": "Streaming partial parameters for a tool call.", + "description": "The archived state of the session changed.\n\nDispatched by a client to archive a session (e.g. the task is\ncomplete) or to unarchive it.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallDelta" - }, - "content": { - "type": "string", - "description": "Partial parameter content to append" + "$ref": "#/$defs/ActionType.SessionIsArchivedChanged" }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Updated progress message" + "isArchived": { + "type": "boolean", + "description": "Whether the session is archived" } }, "required": [ - "turnId", - "toolCallId", "type", - "content" + "isArchived" ] }, - "ChatToolCallReadyAction": { + "SessionActivityChangedAction": { "type": "object", - "description": "Tool call parameters are complete, or a running tool requires re-confirmation.\n\nWhen dispatched for a `streaming` tool call, transitions to `pending-confirmation`\nor directly to `running` if `confirmed` is set.\n\nWhen dispatched for a `running` tool call (e.g. mid-execution permission needed),\ntransitions back to `pending-confirmation`. The `invocationMessage` and `_meta`\nSHOULD be updated to describe the specific confirmation needed. Clients use the\nstandard `chat/toolCallConfirmed` flow to approve or deny.\n\nFor client-provided tools, the server typically sets `confirmed` to\n`'not-needed'` so the tool transitions directly to `running`, where the\nowning client can begin execution immediately.", + "description": "The activity description of the session changed.\n\nDispatched by the server to indicate what the session is currently doing\n(e.g. running a tool, thinking). Clear activity by setting it to `undefined`.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallReady" - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do or what confirmation is needed" + "$ref": "#/$defs/ActionType.SessionActivityChanged" }, - "toolInput": { + "activity": { "type": "string", - "description": "Raw tool input" - }, - "confirmationTitle": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" - }, - "edits": { - "type": "object", - "properties": { - "items": { - "type": "string" - } - }, - "required": [ - "items" - ], - "description": "File edits that this tool call will perform, for preview before confirmation" - }, - "editable": { - "type": "boolean", - "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "If set, the tool was auto-confirmed and transitions directly to `running`" + "description": "Human-readable description of current activity, or `undefined` to clear" + } + }, + "required": [ + "type", + "activity" + ] + }, + "SessionChangesetsChangedAction": { + "type": "object", + "description": "The {@link Changeset | catalogue of changesets} the agent host\nadvertises for this session changed. Replaces\n{@link SessionState.changesets | `state.changesets`} entirely\n(full-replacement semantics) — set to `undefined` to clear the\ncatalogue.\n\nProducers dispatch this whenever entries are added or removed. The\nfan-out happens through this action so observers see catalogue\nmutations in the same {@link ChangesetAction | per-changeset} action\nstream they already follow for file-level updates.", + "properties": { + "type": { + "$ref": "#/$defs/ActionType.SessionChangesetsChanged" }, - "options": { + "changesets": { "type": "array", "items": { - "$ref": "#/$defs/ConfirmationOption" + "$ref": "#/$defs/Changeset" }, - "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." + "description": "New catalogue, or `undefined` to clear it" } }, "required": [ - "turnId", - "toolCallId", "type", - "invocationMessage" + "changesets" ] }, - "ChatToolCallApprovedAction": { + "SessionServerToolsChangedAction": { "type": "object", - "description": "Client approves a pending tool call. The tool transitions to `running`.", + "description": "Server tools for this session have changed.\n\nFull-replacement semantics: the `tools` array replaces the previous `serverTools` entirely.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallConfirmed" - }, - "approved": { - "description": "The tool call was approved" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed" - }, - "editedToolInput": { - "type": "string", - "description": "Edited tool input parameters, if the client modified them before confirming" + "$ref": "#/$defs/ActionType.SessionServerToolsChanged" }, - "selectedOptionId": { - "type": "string", - "description": "ID of the selected confirmation option, if the server provided options" + "tools": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolDefinition" + }, + "description": "Updated server tools list (full replacement)" } }, "required": [ - "turnId", - "toolCallId", "type", - "approved", - "confirmed" + "tools" ] }, - "ChatToolCallDeniedAction": { + "SessionActiveClientChangedAction": { "type": "object", - "description": "Client denies a pending tool call. The tool transitions to `cancelled`.\n\nFor client-provided tools, the owning client MUST dispatch this if it does\nnot recognize the tool or cannot execute it.", + "description": "The active client for this session has changed.\n\nA client dispatches this action with its own `SessionActiveClient` to claim\nthe active role, or with `null` to release it. The server SHOULD reject if\nanother client is already active. The server SHOULD automatically dispatch\nthis action with `activeClient: null` when the active client disconnects.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallConfirmed" - }, - "approved": { - "description": "The tool call was denied" + "$ref": "#/$defs/ActionType.SessionActiveClientChanged" }, - "reason": { + "activeClient": { "oneOf": [ { - "$ref": "#/$defs/ToolCallCancellationReason.Denied" + "$ref": "#/$defs/SessionActiveClient" }, - { - "$ref": "#/$defs/ToolCallCancellationReason.Skipped" - } + {} ], - "description": "Why the tool was cancelled" - }, - "userSuggestion": { - "$ref": "#/$defs/Message", - "description": "What the user suggested doing instead" - }, - "reasonMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Optional explanation for the denial" - }, - "selectedOptionId": { - "type": "string", - "description": "ID of the selected confirmation option, if the server provided options" + "description": "The new active client, or `null` to unset" } }, "required": [ - "turnId", - "toolCallId", "type", - "approved", - "reason" + "activeClient" ] }, - "ChatToolCallCompleteAction": { + "SessionActiveClientToolsChangedAction": { "type": "object", - "description": "Tool execution finished. Transitions to `completed` or `pending-result-confirmation`\nif `requiresResultConfirmation` is `true`.\n\nFor client-provided tools (where `toolClientId` is set on the tool call state),\nthe owning client dispatches this action with the execution result. The server\nSHOULD reject this action if the dispatching client does not match `toolClientId`.\n\nServers waiting on a client tool call MAY time out after a reasonable duration\nif the implementing client disconnects or becomes unresponsive, and dispatch\nthis action with `result.success = false` and an appropriate error.", + "description": "The active client's tool list has changed.\n\nFull-replacement semantics: the `tools` array replaces the active client's\nprevious tools entirely. The server SHOULD reject if the dispatching client\nis not the current active client.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallComplete" - }, - "result": { - "$ref": "#/$defs/ToolCallResult", - "description": "Execution result" + "$ref": "#/$defs/ActionType.SessionActiveClientToolsChanged" }, - "requiresResultConfirmation": { - "type": "boolean", - "description": "If true, the result requires client approval before finalizing" + "tools": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolDefinition" + }, + "description": "Updated client tools list (full replacement)" } }, "required": [ - "turnId", - "toolCallId", "type", - "result" + "tools" ] }, - "ChatToolCallResultConfirmedAction": { + "SessionCustomizationsChangedAction": { "type": "object", - "description": "Client approves or denies a tool's result.\n\nIf `approved` is `false`, the tool transitions to `cancelled` with reason `result-denied`.", + "description": "The session's customizations have changed.\n\nFull-replacement semantics: the `customizations` array replaces the\nprevious `customizations` entirely.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "toolCallId": { - "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, "type": { - "$ref": "#/$defs/ActionType.ChatToolCallResultConfirmed" + "$ref": "#/$defs/ActionType.SessionCustomizationsChanged" }, - "approved": { - "type": "boolean", - "description": "Whether the result was approved" + "customizations": { + "type": "array", + "items": { + "$ref": "#/$defs/Customization" + }, + "description": "Updated customization list (full replacement)." } }, "required": [ - "turnId", - "toolCallId", "type", - "approved" + "customizations" ] }, - "ChatToolCallContentChangedAction": { + "SessionCustomizationToggledAction": { "type": "object", - "description": "Partial content produced while a tool is still executing.\n\nReplaces the `content` array on the running tool call state. Clients can\nuse this to display live feedback (e.g. a terminal reference) before the\ntool completes.\n\nFor client-provided tools (where `toolClientId` is set on the tool call state),\nthe owning client dispatches this action to stream intermediate content while\nexecuting. The server SHOULD reject this action if the dispatching client does\nnot match `toolClientId`.", + "description": "A client toggled a container customization on or off.\n\nTargets a top-level container (plugin or directory) by `id`. Only\ncontainers have an `enabled` flag; children are always active when\ntheir container is enabled. Is a no-op when no matching container is\nfound.", "properties": { - "turnId": { - "type": "string", - "description": "Turn identifier" + "type": { + "$ref": "#/$defs/ActionType.SessionCustomizationToggled" }, - "toolCallId": { + "id": { "type": "string", - "description": "Tool call identifier" - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nClients MAY look for well-known keys here to provide enhanced UI.\nFor example, a `ptyTerminal` key with `{ input: string; output: string }`\nindicates the tool operated on a terminal (both `input` and `output` may\ncontain escape sequences)." - }, - "type": { - "$ref": "#/$defs/ActionType.ChatToolCallContentChanged" + "description": "The id of the container to toggle." }, - "content": { - "type": "array", - "items": { - "$ref": "#/$defs/ToolResultContent" - }, - "description": "The current partial content for the running tool call" + "enabled": { + "type": "boolean", + "description": "Whether to enable or disable the container." } }, "required": [ - "turnId", - "toolCallId", "type", - "content" + "id", + "enabled" ] }, - "ChatTurnCompleteAction": { + "SessionCustomizationUpdatedAction": { "type": "object", - "description": "Turn finished — the assistant is idle.", + "description": "Upserts a top-level customization (plugin or directory).\n\nThe reducer locates the existing entry by `customization.id`:\n\n- If found, the entry is replaced entirely with `customization`,\n including its `children` array. To preserve existing children, the\n host must include them on the payload.\n- If not found, the entry is appended.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatTurnComplete" + "$ref": "#/$defs/ActionType.SessionCustomizationUpdated" }, - "turnId": { - "type": "string", - "description": "Turn identifier" + "customization": { + "$ref": "#/$defs/Customization", + "description": "The customization to upsert (matched by `customization.id`)." } }, "required": [ "type", - "turnId" + "customization" ] }, - "ChatTurnCancelledAction": { + "SessionCustomizationRemovedAction": { "type": "object", - "description": "Turn was aborted; server stops processing.", + "description": "Removes a customization by id.\n\nSearches every container and its children for the entry. If the entry\nis a container, its children are removed with it. Is a no-op when no\nmatching id is found.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatTurnCancelled" + "$ref": "#/$defs/ActionType.SessionCustomizationRemoved" }, - "turnId": { + "id": { "type": "string", - "description": "Turn identifier" + "description": "The id of the customization to remove." } }, "required": [ "type", - "turnId" + "id" ] }, - "ChatErrorAction": { + "SessionMcpServerStateChangedAction": { "type": "object", - "description": "Error during turn processing.", + "description": "Updates the runtime fields of an existing\n{@link McpServerCustomization} — narrow alternative to\n{@link SessionCustomizationUpdatedAction} for the high-frequency\n`starting` ↔ `ready` ↔ `authRequired` transitions.\n\nLocates the target entry by `id`, searching both the top-level\ncustomization list and the `children` array of every container.\nReplaces the entry's {@link McpServerCustomization.state | `state`}\nand {@link McpServerCustomization.channel | `channel`}\n(full-replacement semantics: omit `channel` to clear an existing\nchannel URI). Other fields of the customization are preserved.\n\nIs a no-op when no matching `McpServerCustomization` is found. To\nupdate any other field (name, icons, `mcpApp` capabilities, etc.) use\n{@link SessionCustomizationUpdatedAction} instead.\n\nWhen the transition is to {@link McpServerStatus.AuthRequired}\nbecause of a request issued mid-turn, the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session — see\n{@link McpServerAuthRequiredState} for the rationale.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatError" + "$ref": "#/$defs/ActionType.SessionMcpServerStateChanged" }, - "turnId": { + "id": { "type": "string", - "description": "Turn identifier" + "description": "The id of the {@link McpServerCustomization} to update." }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details" + "state": { + "$ref": "#/$defs/McpServerState", + "description": "The new lifecycle state." + }, + "channel": { + "$ref": "#/$defs/URI", + "description": "Updated `mcp://` side-channel URI. Full-replacement: omit to clear\nan existing channel (typical when leaving\n{@link McpServerStatus.Ready | `Ready`})." } }, "required": [ "type", - "turnId", - "error" + "id", + "state" ] }, - "ChatUsageAction": { + "SessionConfigChangedAction": { "type": "object", - "description": "Token usage report for a turn.", + "description": "Client changed a mutable config value mid-session.\n\nOnly properties with `sessionMutable: true` in the config schema may be\nchanged. The server validates and broadcasts the action; the reducer merges\nthe new values into `state.config.values`.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatUsage" + "$ref": "#/$defs/ActionType.SessionConfigChanged" }, - "turnId": { - "type": "string", - "description": "Turn identifier" + "config": { + "type": "object", + "additionalProperties": {}, + "description": "Updated config values" }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage data" + "replace": { + "type": "boolean", + "description": "When `true`, replaces all config values instead of merging" } }, "required": [ "type", - "turnId", - "usage" + "config" ] }, - "ChatReasoningAction": { + "SessionMetaChangedAction": { "type": "object", - "description": "Reasoning/thinking text from the model, appended to a specific reasoning response part.\n\nThe server MUST first emit a `chat/responsePart` to create the target\nreasoning part, then use this action to append text to it.", + "description": "The session's `_meta` side-channel changed. Replaces `state._meta`\nentirely (full-replacement semantics). Producers SHOULD merge any\nkeys they wish to preserve into the new value before dispatching.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatReasoning" - }, - "turnId": { - "type": "string", - "description": "Turn identifier" - }, - "partId": { - "type": "string", - "description": "Identifier of the reasoning response part to append to" + "$ref": "#/$defs/ActionType.SessionMetaChanged" }, - "content": { - "type": "string", - "description": "Reasoning text chunk" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "New `_meta` payload, or `undefined` to clear it" } }, "required": [ "type", - "turnId", - "partId", - "content" + "_meta" ] }, - "ChatTruncatedAction": { + "SessionTruncatedAction": { "type": "object", - "description": "Truncates a session's history. If `turnId` is provided, all turns after that\nturn are removed and the specified turn is kept. If `turnId` is omitted, all\nturns are removed.\n\nIf there is an active turn it is silently dropped and the chat status\nreturns to `idle`.\n\nCommon use-case: truncate old data then dispatch a new\n`chat/turnStarted` with an edited message.", + "description": "Truncates a session's history. If `turnId` is provided, all turns after that\nturn are removed and the specified turn is kept. If `turnId` is omitted, all\nturns are removed.\n\nIf there is an active turn it is silently dropped and the session status\nreturns to `idle`.\n\nCommon use-case: truncate old data then dispatch a new\n`session/turnStarted` with an edited message.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatTruncated" + "$ref": "#/$defs/ActionType.SessionTruncated" }, "turnId": { "type": "string", @@ -6425,12 +6136,12 @@ "type" ] }, - "ChatPendingMessageSetAction": { + "SessionPendingMessageSetAction": { "type": "object", - "description": "A pending message was set (upsert semantics: creates or replaces).\n\nFor steering messages, this always replaces the single steering message.\nFor queued messages, if a message with the given `id` already exists it is\nupdated in place; otherwise it is appended to the queue. If the chat is\nidle when a queued message is set, the server SHOULD immediately consume it\nand start a new turn.\n\nA client is only allowed to send {@link MessageKind.User} messages.", + "description": "A pending message was set (upsert semantics: creates or replaces).\n\nFor steering messages, this always replaces the single steering message.\nFor queued messages, if a message with the given `id` already exists it is\nupdated in place; otherwise it is appended to the queue. If the session is\nidle when a queued message is set, the server SHOULD immediately consume it\nand start a new turn.\n\nA client is only allowed to send {@link MessageKind.User} messages.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatPendingMessageSet" + "$ref": "#/$defs/ActionType.SessionPendingMessageSet" }, "kind": { "$ref": "#/$defs/PendingMessageKind", @@ -6452,12 +6163,12 @@ "message" ] }, - "ChatPendingMessageRemovedAction": { + "SessionPendingMessageRemovedAction": { "type": "object", "description": "A pending message was removed (steering or queued).\n\nDispatched by clients to cancel a pending message, or by the server when\nit consumes a message (e.g. starting a turn from a queued message or\ninjecting a steering message into the current turn).", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatPendingMessageRemoved" + "$ref": "#/$defs/ActionType.SessionPendingMessageRemoved" }, "kind": { "$ref": "#/$defs/PendingMessageKind", @@ -6474,12 +6185,12 @@ "id" ] }, - "ChatQueuedMessagesReorderedAction": { + "SessionQueuedMessagesReorderedAction": { "type": "object", "description": "Reorder the queued messages.\n\nThe `order` array contains the IDs of queued messages in their new\ndesired order. IDs not present in the current queue are ignored.\nQueued messages whose IDs are absent from `order` are appended at\nthe end in their original relative order (so a client with a stale\nview of the queue never silently drops messages).", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatQueuedMessagesReordered" + "$ref": "#/$defs/ActionType.SessionQueuedMessagesReordered" }, "order": { "type": "array", @@ -6494,15 +6205,15 @@ "order" ] }, - "ChatInputRequestedAction": { + "SessionInputRequestedAction": { "type": "object", "description": "A session requested input from the user.\n\nFull-request upsert semantics: the `request` replaces any existing request\nwith the same `id`, or is appended if it is new. Answer drafts are preserved\nunless `request.answers` is provided.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatInputRequested" + "$ref": "#/$defs/ActionType.SessionInputRequested" }, "request": { - "$ref": "#/$defs/ChatInputRequest", + "$ref": "#/$defs/SessionInputRequest", "description": "Input request to create or replace" } }, @@ -6511,12 +6222,12 @@ "request" ] }, - "ChatInputAnswerChangedAction": { + "SessionInputAnswerChangedAction": { "type": "object", "description": "A client updated, submitted, skipped, or removed a single in-progress answer.\n\nDispatching with `answer: undefined` removes that question's answer draft.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatInputAnswerChanged" + "$ref": "#/$defs/ActionType.SessionInputAnswerChanged" }, "requestId": { "type": "string", @@ -6527,7 +6238,7 @@ "description": "Question identifier within the input request" }, "answer": { - "$ref": "#/$defs/ChatInputAnswer", + "$ref": "#/$defs/SessionInputAnswer", "description": "Updated answer, or `undefined` to clear an answer draft" } }, @@ -6537,25 +6248,25 @@ "questionId" ] }, - "ChatInputCompletedAction": { + "SessionInputCompletedAction": { "type": "object", "description": "A client accepted, declined, or cancelled a session input request.\n\nIf accepted, the server uses `answers` (when provided) plus the request's\nsynced answer state to resume the blocked operation.", "properties": { "type": { - "$ref": "#/$defs/ActionType.ChatInputCompleted" + "$ref": "#/$defs/ActionType.SessionInputCompleted" }, "requestId": { "type": "string", "description": "Input request identifier" }, "response": { - "$ref": "#/$defs/ChatInputResponseKind", + "$ref": "#/$defs/SessionInputResponseKind", "description": "Completion outcome" }, "answers": { "type": "object", "additionalProperties": { - "$ref": "#/$defs/ChatInputAnswer" + "$ref": "#/$defs/SessionInputAnswer" }, "description": "Optional final answer replacement, keyed by question ID" } diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 658b3207..e3e5ab92 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -484,7 +484,7 @@ "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`)" + "description": "The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`)" }, "state": { "oneOf": [ @@ -682,6 +682,24 @@ "values" ] }, + "PendingMessage": { + "type": "object", + "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this pending message" + }, + "message": { + "$ref": "#/$defs/Message", + "description": "The message that will start the next turn" + } + }, + "required": [ + "id", + "message" + ] + }, "SessionState": { "type": "object", "description": "Full state for a single session, loaded when a client subscribes to the session's URI.", @@ -709,16 +727,34 @@ "$ref": "#/$defs/SessionActiveClient", "description": "The client currently providing tools and interactive capabilities to this session" }, - "chats": { + "turns": { "type": "array", "items": { - "$ref": "#/$defs/ChatSummary" + "$ref": "#/$defs/Turn" }, - "description": "Catalog of chats in this session." + "description": "Completed turns" }, - "defaultChat": { - "$ref": "#/$defs/URI", - "description": "The chat that receives input when the user addresses the session without\nselecting a specific chat. This is a UI routing hint, not a hierarchy\nmarker — chats remain equal peers at the protocol level. Hosts MAY change\nthis over the session's lifetime." + "activeTurn": { + "$ref": "#/$defs/ActiveTurn", + "description": "Currently in-progress turn" + }, + "steeringMessage": { + "$ref": "#/$defs/PendingMessage", + "description": "Message to inject into the current turn at a convenient point" + }, + "queuedMessages": { + "type": "array", + "items": { + "$ref": "#/$defs/PendingMessage" + }, + "description": "Messages to send automatically as new turns after the current turn finishes" + }, + "inputRequests": { + "type": "array", + "items": { + "$ref": "#/$defs/SessionInputRequest" + }, + "description": "Requests for user input that are currently blocking or informing session progress" }, "config": { "$ref": "#/$defs/SessionConfigState", @@ -747,7 +783,7 @@ "required": [ "summary", "lifecycle", - "chats" + "turns" ] }, "SessionActiveClient": { @@ -802,7 +838,6 @@ }, "SessionSummary": { "type": "object", - "description": "Lightweight catalog entry summarizing one session. Surfaced via\n{@link RootChannelCommands.listSessions | `root/listSessions`} and\n`root/sessionAdded`/`root/sessionSummaryChanged` notifications.\n\n**Aggregation across chats.** Once a session contains more than one chat,\nseveral `SessionSummary` fields are derived from the underlying\n{@link SessionState.chats | chat catalog}. Producers SHOULD follow these\nrules so clients that only consume the session summary (e.g. a session\nlist) still see meaningful state:\n\n- `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` /\n `Error` — bits 0–4) from the\n {@link SessionState.defaultChat | default chat} when present, else from\n the most recently modified chat. **Promote** `InputNeeded` whenever any\n chat in the session needs input, and **promote** `Error` whenever any\n chat is in an error state — both override the default-chat bits. The\n orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped.\n- `activity`: mirror the activity string of the default chat, or of the\n chat currently driving the promoted status bits when a non-default chat\n wins (e.g. the chat that raised `InputNeeded`).\n- `modifiedAt`: the max of all chats' `modifiedAt`.\n- `model` / `agent`: the session-level selection. Per-chat overrides are\n surfaced on individual {@link ChatSummary} entries, not aggregated up.\n- `workingDirectory`: the session-level **default**. Individual chats MAY\n override via {@link ChatSummary.workingDirectory}; aggregating these up\n is meaningless and SHOULD NOT be attempted.\n- `changes`: optional roll-up across all chats. Producers MAY sum the\n per-chat changeset stats or report the most expensive chat's stats —\n whichever is cheaper for the host to compute.\n\nSessions with a single chat trivially satisfy all of the above (the chat's\nvalues pass through unchanged). The rules only matter once a session\ncarries multiple chats.", "properties": { "resource": { "$ref": "#/$defs/URI", @@ -846,7 +881,7 @@ }, "workingDirectory": { "$ref": "#/$defs/URI", - "description": "The default working directory URI for this session. Individual chats\nMAY override via {@link ChatSummary.workingDirectory | their own\n`workingDirectory`}; this field acts as the fallback for any chat that\ndoes not." + "description": "The working directory URI for this session" }, "changes": { "$ref": "#/$defs/ChangesSummary", @@ -1030,1774 +1065,1570 @@ "values" ] }, - "ToolDefinition": { + "SessionInputOption": { "type": "object", - "description": "Describes a tool available in a session, provided by either the server or the active client.", + "description": "A choice in a select-style question.", "properties": { - "name": { + "id": { "type": "string", - "description": "Unique tool identifier" + "description": "Stable option identifier; for MCP enum values this is the enum string" }, - "title": { + "label": { "type": "string", - "description": "Human-readable display name" + "description": "Display label" }, "description": { "type": "string", - "description": "Description of what the tool does" - }, - "inputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." - }, - "outputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." - }, - "annotations": { - "$ref": "#/$defs/ToolAnnotations", - "description": "Behavioral hints about the tool. All properties are advisory." + "description": "Optional secondary text" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." + "recommended": { + "type": "boolean", + "description": "Whether this option is the recommended/default choice" } }, "required": [ - "name" + "id", + "label" ] }, - "ToolAnnotations": { + "SessionInputQuestionBase": { "type": "object", - "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "title": { + "id": { "type": "string", - "description": "Alternate human-readable title" - }, - "readOnlyHint": { - "type": "boolean", - "description": "Tool does not modify its environment (default: false)" + "description": "Stable question identifier used as the key in `answers`" }, - "destructiveHint": { - "type": "boolean", - "description": "Tool may perform destructive updates (default: true)" + "title": { + "type": "string", + "description": "Short display title" }, - "idempotentHint": { - "type": "boolean", - "description": "Repeated calls with the same arguments have no additional effect (default: false)" + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "openWorldHint": { + "required": { "type": "boolean", - "description": "Tool may interact with external entities (default: true)" + "description": "Whether the user must answer this question to accept the request" } - } + }, + "required": [ + "id", + "message" + ] }, - "CustomizationBase": { + "SessionInputTextQuestion": { "type": "object", - "description": "Fields shared by every customization variant.", + "description": "Text question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "Stable question identifier used as the key in `answers`" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "title": { + "type": "string", + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Prompt shown to the user" }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.Text" + }, + "format": { + "type": "string", + "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + }, + "min": { + "type": "number", + "description": "Minimum string length" + }, + "max": { + "type": "number", + "description": "Maximum string length" + }, + "defaultValue": { + "type": "string", + "description": "Default text" } }, "required": [ "id", - "uri", - "name" + "message", + "kind" ] }, - "CustomizationLoadingState": { + "SessionInputNumberQuestion": { "type": "object", - "description": "Container is being loaded by the host.", + "description": "Numeric question within a session input request.", "properties": { + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" + }, + "message": { + "type": "string", + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loading" + "oneOf": [ + { + "$ref": "#/$defs/SessionInputQuestionKind.Number" + }, + { + "$ref": "#/$defs/SessionInputQuestionKind.Integer" + } + ] + }, + "min": { + "type": "number", + "description": "Minimum value" + }, + "max": { + "type": "number", + "description": "Maximum value" + }, + "defaultValue": { + "type": "number", + "description": "Default numeric value" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationLoadedState": { + "SessionInputBooleanQuestion": { "type": "object", - "description": "Container loaded successfully.", + "description": "Boolean question within a session input request.", "properties": { + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" + }, + "message": { + "type": "string", + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loaded" + "$ref": "#/$defs/SessionInputQuestionKind.Boolean" + }, + "defaultValue": { + "type": "boolean", + "description": "Default boolean value" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationDegradedState": { + "SessionInputSingleSelectQuestion": { "type": "object", - "description": "Container partially loaded but has warnings.", + "description": "Single-select question within a session input request.", "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Degraded" - }, - "message": { + "id": { "type": "string", - "description": "Human-readable description of the warning." - } - }, - "required": [ - "kind", - "message" - ] - }, - "CustomizationErrorState": { - "type": "object", - "description": "Container failed to load.", - "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Error" + "description": "Stable question identifier used as the key in `answers`" }, - "message": { - "type": "string", - "description": "Human-readable error message." - } - }, - "required": [ - "kind", - "message" - ] - }, - "ContainerCustomizationBase": { - "type": "object", - "description": "Fields shared by container customizations.", - "properties": { - "id": { + "title": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.SingleSelect" }, - "children": { + "options": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Options the user may select from" + }, + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text instead of selecting an option" } }, "required": [ "id", - "uri", - "name", - "enabled" + "message", + "kind", + "options" ] }, - "PluginCustomization": { + "SessionInputMultiSelectQuestion": { "type": "object", - "description": "An [Open Plugins](https://open-plugins.com/) plugin.", + "description": "Multi-select question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable question identifier used as the key in `answers`" }, - "name": { + "title": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Short display title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.MultiSelect" }, - "children": { + "options": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Options the user may select from" }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text in addition to selecting options" + }, + "min": { + "type": "number", + "description": "Minimum selected item count" + }, + "max": { + "type": "number", + "description": "Maximum selected item count" } }, "required": [ "id", - "uri", - "name", - "enabled", - "type" + "message", + "kind", + "options" ] }, - "ClientPluginCustomization": { + "SessionInputRequest": { "type": "object", - "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", + "description": "A live request for user input.\n\nThe server creates or replaces requests with `session/inputRequested`.\nClients sync drafts with `session/inputAnswerChanged` and complete requests\nwith `session/inputCompleted`.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable request identifier" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Display message for the request as a whole" }, - "icons": { + "url": { + "$ref": "#/$defs/URI", + "description": "URL the user should review or open, for URL-style elicitations" + }, + "questions": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/SessionInputQuestion" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." - }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "description": "Ordered questions to ask the user" }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" + "answers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/SessionInputAnswer" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Current draft or submitted answers, keyed by question ID" + } + }, + "required": [ + "id" + ] + }, + "SessionInputTextAnswerValue": { + "type": "object", + "description": "Value captured for one answer.", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Text" }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputNumberAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Number" }, - "nonce": { - "type": "string", - "description": "Opaque version token used by the host to detect changes." + "value": { + "type": "number" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type" + "kind", + "value" ] }, - "DirectoryCustomization": { + "SessionInputBooleanAnswerValue": { "type": "object", - "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Boolean" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "value": { + "type": "boolean" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputSelectedAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Selected" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "string" }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." - }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." - }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" - }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Directory" - }, - "contents": { - "$ref": "#/$defs/ChildCustomizationType", - "description": "Which child customization type this directory holds." - }, - "writable": { - "type": "boolean", - "description": "Whether clients may write into this directory." + "description": "Free-form text entered instead of selecting an option" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type", - "contents", - "writable" + "kind", + "value" ] }, - "AgentCustomization": { + "SessionInputSelectedManyAnswerValue": { "type": "object", - "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.SelectedMany" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "array", + "items": { + "type": "string" + } }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Agent" - }, - "description": { - "type": "string", - "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." + "description": "Free-form text entered in addition to selected options" } }, "required": [ - "id", - "uri", - "name", - "type" + "kind", + "value" ] }, - "SkillCustomization": { + "SessionInputAnswered": { "type": "object", - "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "state": { + "oneOf": [ + { + "$ref": "#/$defs/SessionInputAnswerState.Draft" + }, + { + "$ref": "#/$defs/SessionInputAnswerState.Submitted" + } + ], + "description": "Answer state" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "$ref": "#/$defs/SessionInputAnswerValue", + "description": "Answer value" + } + }, + "required": [ + "state", + "value" + ] + }, + "SessionInputSkipped": { + "type": "object", + "properties": { + "state": { + "$ref": "#/$defs/SessionInputAnswerState.Skipped", + "description": "Answer state" }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Skill" - }, - "description": { - "type": "string", - "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." - }, - "disableModelInvocation": { - "type": "boolean", - "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." + "description": "Free-form reason or value captured while skipping, if any" } }, "required": [ - "id", - "uri", - "name", - "type" + "state" ] }, - "PromptCustomization": { + "Turn": { "type": "object", - "description": "A prompt contributed by a plugin or directory.", + "description": "A completed request/response cycle.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" }, - "type": { - "$ref": "#/$defs/CustomizationType.Prompt" + "state": { + "$ref": "#/$defs/TurnState", + "description": "How the turn ended" }, - "description": { - "type": "string", - "description": "Short description of what the prompt does." + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details if state is `'error'`" } }, "required": [ "id", - "uri", - "name", - "type" + "message", + "responseParts", + "usage", + "state" ] }, - "RuleCustomization": { + "ActiveTurn": { "type": "object", - "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", + "description": "An in-progress turn — the assistant is actively streaming.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Rule" - }, - "description": { - "type": "string", - "description": "Description of what the rule enforces." - }, - "alwaysApply": { - "type": "boolean", - "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." }, - "globs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" } }, "required": [ "id", - "uri", - "name", - "type" + "message", + "responseParts", + "usage" ] }, - "HookCustomization": { + "Message": { "type": "object", - "description": "A hook manifest contributed by a plugin or directory.", + "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", "properties": { - "id": { + "text": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Message text" }, - "name": { - "type": "string", - "description": "Human-readable name." + "origin": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + }, + "required": [ + "kind" + ], + "description": "The origin of the message" }, - "icons": { + "attachments": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/MessageAttachment" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "File/selection attachments" }, - "type": { - "$ref": "#/$defs/CustomizationType.Hook" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." } }, "required": [ - "id", - "uri", - "name", - "type" + "text", + "origin" ] }, - "McpServerCustomization": { + "MessageAttachmentBase": { "type": "object", - "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", + "description": "Common fields shared by all {@link MessageAttachment} variants.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." - }, - "name": { + "label": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.McpServer" - }, - "enabled": { - "type": "boolean", - "description": "Whether this MCP server is currently enabled." - }, - "state": { - "$ref": "#/$defs/McpServerState", - "description": "Current lifecycle state of the MCP server." + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "channel": { - "$ref": "#/$defs/URI", - "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "mcpApp": { - "$ref": "#/$defs/McpServerCustomizationApps", - "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." } }, "required": [ - "id", - "uri", - "name", - "type", - "enabled", - "state" + "label" ] }, - "McpServerCustomizationApps": { + "SimpleMessageAttachment": { "type": "object", - "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", + "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", "properties": { - "capabilities": { - "$ref": "#/$defs/AhpMcpUiHostCapabilities", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Simple", + "description": "Discriminant" + }, + "modelRepresentation": { + "type": "string", + "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." } }, "required": [ - "capabilities" + "label", + "type" ] }, - "AhpMcpUiHostCapabilities": { + "MessageEmbeddedResourceAttachment": { "type": "object", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", + "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", "properties": { - "serverTools": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `tools/*` methods to the upstream server." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "serverResources": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `resources/*` methods to the upstream server." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "logging": { + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { "type": "object", "additionalProperties": {}, - "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, - "sampling": { - "type": "object", - "properties": { - "tools": { - "type": "string" - } - }, - "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." - } - } - }, - "McpServerStartingState": { - "type": "object", - "description": "Server is registered with the host but has not yet started.", - "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.Starting" + "type": { + "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", + "description": "Discriminant" + }, + "data": { + "type": "string", + "description": "Base64-encoded binary data" + }, + "contentType": { + "type": "string", + "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + }, + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "kind" + "label", + "type", + "data", + "contentType" ] }, - "McpServerReadyState": { + "MessageResourceAttachment": { "type": "object", - "description": "Server is running and serving requests.", + "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.Ready" + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Resource", + "description": "Discriminant" + }, + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "kind" + "label", + "uri", + "type" ] }, - "McpServerAuthRequiredState": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.AuthRequired" - }, - "reason": { - "$ref": "#/$defs/McpAuthRequiredReason", - "description": "Why authentication is required." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Annotations", + "description": "Discriminant" }, "resource": { - "$ref": "#/$defs/ProtectedResourceMetadata", - "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." + "$ref": "#/$defs/URI", + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "requiredScopes": { + "annotationIds": { "type": "array", "items": { "type": "string" }, - "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." - }, - "description": { - "type": "string", - "description": "Human-readable hint, typically from the OAuth `error_description`." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ - "kind", - "reason", + "label", + "type", "resource" ] }, - "McpServerErrorState": { + "MarkdownResponsePart": { "type": "object", - "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Error" + "$ref": "#/$defs/ResponsePartKind.Markdown", + "description": "Discriminant" }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details." + "id": { + "type": "string", + "description": "Part identifier, used by `session/delta` to target this part for content appends" + }, + "content": { + "type": "string", + "description": "Markdown content" } }, "required": [ "kind", - "error" + "id", + "content" ] }, - "McpServerStoppedState": { + "ResourceReponsePart": { "type": "object", - "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", + "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" + }, "kind": { - "$ref": "#/$defs/McpServerStatus.Stopped" + "$ref": "#/$defs/ResponsePartKind.ContentRef", + "description": "Discriminant" } }, "required": [ + "uri", "kind" ] }, - "ChatState": { + "ToolCallResponsePart": { "type": "object", - "description": "Full state for a single chat, loaded when a client subscribes to the chat's\nURI.\n\nThe lightweight catalog representation of a chat is {@link ChatSummary},\ncarried in {@link SessionState.chats | `SessionState.chats`}. `ChatState`\n**denormalizes** every {@link ChatSummary} field directly onto itself so\nsubscribers receive one flat object instead of having to merge a nested\n`summary` sub-object. Producers MUST keep the two representations\nconsistent: any change to the inlined fields below SHOULD also be\nannounced on the parent session via the matching\n{@link SessionChatUpdatedAction | `session/chatUpdated`} action.", + "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { - "type": "string", - "description": "Human-readable description of what the chat is currently doing" - }, - "modifiedAt": { - "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nHosts MAY override this for individual chats — for example, to give a\nsubordinate chat its own git worktree so multiple chats in a session can\nmake independent edits that the orchestrator later merges back." - }, - "turns": { - "type": "array", - "items": { - "$ref": "#/$defs/Turn" - }, - "description": "Completed turns" - }, - "activeTurn": { - "$ref": "#/$defs/ActiveTurn", - "description": "Currently in-progress turn" - }, - "steeringMessage": { - "$ref": "#/$defs/PendingMessage", - "description": "Message to inject into the current turn at a convenient point" - }, - "queuedMessages": { - "type": "array", - "items": { - "$ref": "#/$defs/PendingMessage" - }, - "description": "Messages to send automatically as new turns after the current turn finishes" - }, - "inputRequests": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputRequest" - }, - "description": "Requests for user input that are currently blocking or informing chat progress" + "kind": { + "$ref": "#/$defs/ResponsePartKind.ToolCall", + "description": "Discriminant" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this chat." + "toolCall": { + "$ref": "#/$defs/ToolCallState", + "description": "Full tool call lifecycle state" } }, "required": [ - "resource", - "title", - "status", - "modifiedAt", - "turns" + "kind", + "toolCall" ] }, - "ChatSummary": { + "ReasoningResponsePart": { "type": "object", - "description": "Lightweight catalog entry for a chat, carried in\n{@link SessionState.chats | `SessionState.chats`}. The full conversation\nlives in {@link ChatState}, which inlines (denormalizes) every field below.", + "description": "Reasoning/thinking content from the model.", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" + "kind": { + "$ref": "#/$defs/ResponsePartKind.Reasoning", + "description": "Discriminant" }, - "activity": { + "id": { "type": "string", - "description": "Human-readable description of what the chat is currently doing" + "description": "Part identifier, used by `session/reasoning` to target this part for content appends" }, - "modifiedAt": { + "content": { "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nSee {@link ChatState.workingDirectory} for usage notes." + "description": "Accumulated reasoning text" } }, "required": [ - "resource", - "title", - "status", - "modifiedAt" + "kind", + "id", + "content" ] }, - "PendingMessage": { + "SystemNotificationResponsePart": { "type": "object", - "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", + "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", "properties": { - "id": { - "type": "string", - "description": "Unique identifier for this pending message" + "kind": { + "$ref": "#/$defs/ResponsePartKind.SystemNotification", + "description": "Discriminant" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that will start the next turn" + "content": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "The text of the system notification" } }, "required": [ - "id", - "message" + "kind", + "content" ] }, - "ChatInputOption": { + "ConfirmationOption": { "type": "object", - "description": "A choice in a select-style question.", + "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", "properties": { "id": { "type": "string", - "description": "Stable option identifier; for MCP enum values this is the enum string" + "description": "Unique identifier for the option, returned in the confirmed action" }, "label": { "type": "string", - "description": "Display label" + "description": "Human-readable label displayed to the user" }, - "description": { - "type": "string", - "description": "Optional secondary text" + "kind": { + "$ref": "#/$defs/ConfirmationOptionKind", + "description": "Whether this option represents an approval or denial" }, - "recommended": { - "type": "boolean", - "description": "Whether this option is the recommended/default choice" + "group": { + "type": "number", + "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." } }, "required": [ "id", - "label" + "label", + "kind" ] }, - "ChatInputQuestionBase": { + "ToolCallClientContributor": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.Client" }, - "title": { + "clientId": { "type": "string", - "description": "Short display title" + "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `session/toolCallComplete` with the result." + } + }, + "required": [ + "kind", + "clientId" + ] + }, + "ToolCallMcpContributor": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.MCP" }, - "message": { + "customizationId": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." } }, "required": [ - "id", - "message" + "kind", + "customizationId" ] }, - "ChatInputTextQuestion": { + "ToolCallBase": { "type": "object", - "description": "Text question within a chat input request.", + "description": "Metadata common to all tool call states.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" + "description": "Unique tool call identifier" }, - "message": { + "toolName": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" - }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Text" + "description": "Internal tool name (for debugging/logging)" }, - "format": { + "displayName": { "type": "string", - "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + "description": "Human-readable tool name" }, - "min": { - "type": "number", - "description": "Minimum string length" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "max": { - "type": "number", - "description": "Maximum string length" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + } + }, + "required": [ + "toolCallId", + "toolName", + "displayName" + ] + }, + "ToolCallParameterFields": { + "type": "object", + "description": "Properties available once tool call parameters are fully received.", + "properties": { + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "defaultValue": { + "toolInput": { "type": "string", - "description": "Default text" + "description": "Raw tool input" } }, "required": [ - "id", - "message", - "kind" + "invocationMessage" ] }, - "ChatInputNumberQuestion": { + "ToolCallResult": { "type": "object", - "description": "Numeric question within a chat input request.", + "description": "Tool execution result details, available after execution completes.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" }, - "title": { - "type": "string", - "description": "Short display title" + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" }, - "message": { - "type": "string", - "description": "Prompt shown to the user" + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." }, - "kind": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputQuestionKind.Number" + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" }, - { - "$ref": "#/$defs/ChatInputQuestionKind.Integer" + "code": { + "type": "string" } - ] - }, - "min": { - "type": "number", - "description": "Minimum value" - }, - "max": { - "type": "number", - "description": "Maximum value" - }, - "defaultValue": { - "type": "number", - "description": "Default numeric value" + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" } }, "required": [ - "id", - "message", - "kind" + "success", + "pastTenseMessage" ] }, - "ChatInputBooleanQuestion": { + "ToolCallStreamingState": { "type": "object", - "description": "Boolean question within a chat input request.", + "description": "LM is streaming the tool call parameters.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Boolean" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "defaultValue": { - "type": "boolean", - "description": "Default boolean value" + "status": { + "$ref": "#/$defs/ToolCallStatus.Streaming" + }, + "partialInput": { + "type": "string", + "description": "Partial parameters accumulated so far" + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Progress message shown while parameters are streaming" } }, "required": [ - "id", - "message", - "kind" + "toolCallId", + "toolName", + "displayName", + "status" ] }, - "ChatInputSingleSelectQuestion": { + "ToolCallPendingConfirmationState": { "type": "object", - "description": "Single-select question within a chat input request.", + "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.SingleSelect" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" + }, + "confirmationTitle": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" + }, + "edits": { + "type": "object", + "properties": { + "items": { + "type": "string" + } + }, + "required": [ + "items" + ], + "description": "File edits that this tool call will perform, for preview before confirmation" + }, + "editable": { + "type": "boolean", + "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" }, "options": { "type": "array", "items": { - "$ref": "#/$defs/ChatInputOption" + "$ref": "#/$defs/ConfirmationOption" }, - "description": "Options the user may select from" - }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text instead of selecting an option" + "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." } }, "required": [ - "id", - "message", - "kind", - "options" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status" ] }, - "ChatInputMultiSelectQuestion": { + "ToolCallRunningState": { "type": "object", - "description": "Multi-select question within a chat input request.", + "description": "Tool is actively executing.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.MultiSelect" - }, - "options": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputOption" - }, - "description": "Options the user may select from" - }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text in addition to selecting options" - }, - "min": { - "type": "number", - "description": "Minimum selected item count" - }, - "max": { - "type": "number", - "description": "Maximum selected item count" - } - }, - "required": [ - "id", - "message", - "kind", - "options" - ] - }, - "ChatInputRequest": { - "type": "object", - "description": "A live request for user input.\n\nThe server creates or replaces requests with `chat/inputRequested`.\nClients sync drafts with `chat/inputAnswerChanged` and complete requests\nwith `chat/inputCompleted`.", - "properties": { - "id": { - "type": "string", - "description": "Stable request identifier" - }, - "message": { - "type": "string", - "description": "Display message for the request as a whole" - }, - "url": { - "$ref": "#/$defs/URI", - "description": "URL the user should review or open, for URL-style elicitations" - }, - "questions": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputQuestion" - }, - "description": "Ordered questions to ask the user" - }, - "answers": { + "_meta": { "type": "object", - "additionalProperties": { - "$ref": "#/$defs/ChatInputAnswer" - }, - "description": "Current draft or submitted answers, keyed by question ID" - } - }, - "required": [ - "id" - ] - }, - "ChatInputTextAnswerValue": { - "type": "object", - "description": "Value captured for one answer.", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Text" - }, - "value": { - "type": "string" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputNumberAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Number" + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "value": { - "type": "number" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputBooleanAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Boolean" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "value": { - "type": "boolean" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Selected" + "toolInput": { + "type": "string", + "description": "Raw tool input" }, - "value": { - "type": "string" + "status": { + "$ref": "#/$defs/ToolCallStatus.Running" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form text entered instead of selecting an option" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedManyAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.SelectedMany" + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" }, - "value": { - "type": "array", - "items": { - "type": "string" - } + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" }, - "freeformValues": { + "content": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/ToolResultContent" }, - "description": "Free-form text entered in addition to selected options" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputAnswered": { - "type": "object", - "properties": { - "state": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputAnswerState.Draft" - }, - { - "$ref": "#/$defs/ChatInputAnswerState.Submitted" - } - ], - "description": "Answer state" - }, - "value": { - "$ref": "#/$defs/ChatInputAnswerValue", - "description": "Answer value" + "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." } }, "required": [ - "state", - "value" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "confirmed" ] }, - "ChatInputSkipped": { + "ToolCallPendingResultConfirmationState": { "type": "object", + "description": "Tool finished executing, waiting for client to approve the result.", "properties": { - "state": { - "$ref": "#/$defs/ChatInputAnswerState.Skipped", - "description": "Answer state" + "toolCallId": { + "type": "string", + "description": "Unique tool call identifier" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form reason or value captured while skipping, if any" - } - }, - "required": [ - "state" - ] - }, - "Turn": { - "type": "object", - "description": "A completed request/response cycle.", - "properties": { - "id": { + "toolName": { "type": "string", - "description": "Turn identifier" + "description": "Internal tool name (for debugging/logging)" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "displayName": { + "type": "string", + "description": "Human-readable tool name" }, - "responseParts": { - "type": "array", - "items": { - "$ref": "#/$defs/ResponsePart" - }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "state": { - "$ref": "#/$defs/TurnState", - "description": "How the turn ended" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details if state is `'error'`" - } - }, - "required": [ - "id", - "message", - "responseParts", - "usage", - "state" - ] - }, - "ActiveTurn": { - "type": "object", - "description": "An in-progress turn — the assistant is actively streaming.", - "properties": { - "id": { + "toolInput": { "type": "string", - "description": "Turn identifier" + "description": "Raw tool input" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" }, - "responseParts": { + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" + }, + "content": { "type": "array", "items": { - "$ref": "#/$defs/ResponsePart" + "$ref": "#/$defs/ToolResultContent" }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" - } - }, - "required": [ - "id", - "message", - "responseParts", - "usage" - ] - }, - "Message": { - "type": "object", - "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", - "properties": { - "text": { - "type": "string", - "description": "Message text" + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." }, - "origin": { + "error": { "type": "object", "properties": { - "kind": { + "message": { + "type": "string" + }, + "code": { "type": "string" } }, - "required": [ - "kind" - ], - "description": "The origin of the message" - }, - "attachments": { - "type": "array", - "items": { - "$ref": "#/$defs/MessageAttachment" - }, - "description": "File/selection attachments" + "required": [ + "message" + ], + "description": "Error details if the tool failed" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "text", - "origin" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "MessageAttachmentBase": { + "ToolCallCompletedState": { "type": "object", - "description": "Common fields shared by all {@link MessageAttachment} variants.", + "description": "Tool completed successfully or with an error.", "properties": { - "label": { + "toolCallId": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool call identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "displayKind": { + "displayName": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - } - }, - "required": [ - "label" - ] - }, - "SimpleMessageAttachment": { - "type": "object", - "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", - "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "displayKind": { + "toolInput": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Raw tool input" }, - "_meta": { + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" + }, + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + }, + "structuredContent": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Simple", - "description": "Discriminant" + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" }, - "modelRepresentation": { - "type": "string", - "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." + "status": { + "$ref": "#/$defs/ToolCallStatus.Completed" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "label", - "type" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "MessageEmbeddedResourceAttachment": { + "ToolCallCancelledState": { "type": "object", - "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", + "description": "Tool call was cancelled before execution.", "properties": { - "label": { + "toolCallId": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool call identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "displayKind": { + "displayName": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", - "description": "Discriminant" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "data": { + "toolInput": { "type": "string", - "description": "Base64-encoded binary data" + "description": "Raw tool input" }, - "contentType": { - "type": "string", - "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + "status": { + "$ref": "#/$defs/ToolCallStatus.Cancelled" }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." + "reason": { + "$ref": "#/$defs/ToolCallCancellationReason", + "description": "Why the tool was cancelled" + }, + "reasonMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Optional message explaining the cancellation" + }, + "userSuggestion": { + "$ref": "#/$defs/Message", + "description": "What the user suggested doing instead" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "label", - "type", - "data", - "contentType" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "reason" ] }, - "MessageResourceAttachment": { + "ToolDefinition": { "type": "object", - "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", + "description": "Describes a tool available in a session, provided by either the server or the active client.", "properties": { - "label": { + "name": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "title": { + "type": "string", + "description": "Human-readable display name" + }, + "description": { + "type": "string", + "description": "Description of what the tool does" + }, + "inputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } + }, + "required": [ + "type" + ], + "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." + }, + "outputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } + }, + "required": [ + "type" + ], + "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "annotations": { + "$ref": "#/$defs/ToolAnnotations", + "description": "Behavioral hints about the tool. All properties are advisory." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Content URI" - }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" - }, - "contentType": { - "type": "string", - "description": "Content MIME type" - }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Resource", - "description": "Discriminant" - }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." + "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." } }, "required": [ - "label", - "uri", - "type" + "name" ] }, - "MessageAnnotationsAttachment": { + "ToolAnnotations": { "type": "object", - "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", + "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "label": { + "title": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Alternate human-readable title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "readOnlyHint": { + "type": "boolean", + "description": "Tool does not modify its environment (default: false)" }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "destructiveHint": { + "type": "boolean", + "description": "Tool may perform destructive updates (default: true)" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "idempotentHint": { + "type": "boolean", + "description": "Repeated calls with the same arguments have no additional effect (default: false)" }, + "openWorldHint": { + "type": "boolean", + "description": "Tool may interact with external entities (default: true)" + } + } + }, + "ToolResultTextContent": { + "type": "object", + "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", + "properties": { "type": { - "$ref": "#/$defs/MessageAttachmentKind.Annotations", - "description": "Discriminant" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." + "$ref": "#/$defs/ToolResultContentType.Text" }, - "annotationIds": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." + "text": { + "type": "string", + "description": "The text content" } }, "required": [ - "label", "type", - "resource" + "text" ] }, - "MarkdownResponsePart": { + "ToolResultEmbeddedResourceContent": { "type": "object", + "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Markdown", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" }, - "id": { + "data": { "type": "string", - "description": "Part identifier, used by `chat/delta` to target this part for content appends" + "description": "Base64-encoded data" }, - "content": { + "contentType": { "type": "string", - "description": "Markdown content" + "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" } }, "required": [ - "kind", - "id", - "content" + "type", + "data", + "contentType" ] }, - "ResourceReponsePart": { + "ToolResultResourceContent": { "type": "object", - "description": "A content part that's a reference to large content stored outside the state tree.", + "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", "properties": { "uri": { "$ref": "#/$defs/URI", @@ -2811,819 +2642,874 @@ "type": "string", "description": "Content MIME type" }, - "kind": { - "$ref": "#/$defs/ResponsePartKind.ContentRef", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Resource" } }, "required": [ "uri", - "kind" + "type" ] }, - "ToolCallResponsePart": { + "ToolResultFileEditContent": { "type": "object", - "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", + "description": "Describes a file modification performed by a tool.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.ToolCall", - "description": "Discriminant" + "before": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state before the edit. Absent for file creations or for in-place file edits." }, - "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "Full tool call lifecycle state" + "after": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state after the edit. Absent for file deletions." + }, + "diff": { + "type": "object", + "properties": { + "added": { + "type": "number" + }, + "removed": { + "type": "number" + } + }, + "description": "Optional diff display metadata" + }, + "type": { + "$ref": "#/$defs/ToolResultContentType.FileEdit" } }, "required": [ - "kind", - "toolCall" + "type" ] }, - "ReasoningResponsePart": { + "ToolResultTerminalContent": { "type": "object", - "description": "Reasoning/thinking content from the model.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Reasoning", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Terminal" }, - "id": { - "type": "string", - "description": "Part identifier, used by `chat/reasoning` to target this part for content appends" + "resource": { + "$ref": "#/$defs/URI", + "description": "Terminal URI (subscribable for full terminal state)" }, - "content": { + "title": { "type": "string", - "description": "Accumulated reasoning text" + "description": "Display title for the terminal content" } }, "required": [ - "kind", - "id", - "content" + "type", + "resource", + "title" ] }, - "SystemNotificationResponsePart": { + "ToolResultSubagentContent": { "type": "object", - "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", + "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.SystemNotification", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Subagent" }, - "content": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "The text of the system notification" + "resource": { + "$ref": "#/$defs/URI", + "description": "Subagent session URI (subscribable for full session state)" + }, + "title": { + "type": "string", + "description": "Display title for the subagent" + }, + "agentName": { + "type": "string", + "description": "Internal agent name" + }, + "description": { + "type": "string", + "description": "Human-readable description of the subagent's task" } }, "required": [ - "kind", - "content" + "type", + "resource", + "title" ] }, - "ConfirmationOption": { + "CustomizationBase": { "type": "object", - "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", + "description": "Fields shared by every customization variant.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the option, returned in the confirmed action" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "label": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Human-readable label displayed to the user" + "description": "Human-readable name." }, - "kind": { - "$ref": "#/$defs/ConfirmationOptionKind", - "description": "Whether this option represents an approval or denial" + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "group": { - "type": "number", - "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." } }, "required": [ "id", - "label", - "kind" + "uri", + "name" ] }, - "ToolCallClientContributor": { + "CustomizationLoadingState": { "type": "object", + "description": "Container is being loaded by the host.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.Client" - }, - "clientId": { - "type": "string", - "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `chat/toolCallComplete` with the result." + "$ref": "#/$defs/CustomizationLoadStatus.Loading" } }, "required": [ - "kind", - "clientId" + "kind" ] }, - "ToolCallMcpContributor": { + "CustomizationLoadedState": { "type": "object", + "description": "Container loaded successfully.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.MCP" - }, - "customizationId": { - "type": "string", - "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." + "$ref": "#/$defs/CustomizationLoadStatus.Loaded" } }, "required": [ - "kind", - "customizationId" + "kind" ] }, - "ToolCallBase": { + "CustomizationDegradedState": { "type": "object", - "description": "Metadata common to all tool call states.", + "description": "Container partially loaded but has warnings.", "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Degraded" }, - "displayName": { + "message": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "description": "Human-readable description of the warning." } }, "required": [ - "toolCallId", - "toolName", - "displayName" + "kind", + "message" ] }, - "ToolCallParameterFields": { + "CustomizationErrorState": { "type": "object", - "description": "Properties available once tool call parameters are fully received.", + "description": "Container failed to load.", "properties": { - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Error" }, - "toolInput": { + "message": { "type": "string", - "description": "Raw tool input" + "description": "Human-readable error message." } }, "required": [ - "invocationMessage" + "kind", + "message" ] }, - "ToolCallResult": { + "ContainerCustomizationBase": { "type": "object", - "description": "Tool execution result details, available after execution completes.", + "description": "Fields shared by container customizations.", "properties": { - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "content": { + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." + }, + "clientId": { + "type": "string", + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + }, + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + }, + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/ChildCustomization" }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." } }, "required": [ - "success", - "pastTenseMessage" + "id", + "uri", + "name", + "enabled" ] }, - "ToolCallStreamingState": { + "PluginCustomization": { "type": "object", - "description": "LM is streaming the tool call parameters.", + "description": "An [Open Plugins](https://open-plugins.com/) plugin.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Streaming" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "partialInput": { + "clientId": { "type": "string", - "description": "Partial parameters accumulated so far" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Progress message shown while parameters are streaming" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + }, + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/ChildCustomization" + }, + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "status" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallPendingConfirmationState": { + "ClientPluginCustomization": { "type": "object", - "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", + "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" - }, - "displayName": { - "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" - }, - "toolInput": { + "id": { "type": "string", - "description": "Raw tool input" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "confirmationTitle": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" + "name": { + "type": "string", + "description": "Human-readable name." }, - "edits": { - "type": "object", - "properties": { - "items": { - "type": "string" - } + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" }, - "required": [ - "items" - ], - "description": "File edits that this tool call will perform, for preview before confirmation" + "description": "Icons for UI display." }, - "editable": { + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "enabled": { "type": "boolean", - "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" + "description": "Whether this container is currently enabled." }, - "options": { + "clientId": { + "type": "string", + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + }, + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + }, + "children": { "type": "array", "items": { - "$ref": "#/$defs/ConfirmationOption" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" + }, + "nonce": { + "type": "string", + "description": "Opaque version token used by the host to detect changes." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallRunningState": { + "DirectoryCustomization": { "type": "object", - "description": "Tool is actively executing.", + "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Running" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "content": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Directory" + }, + "contents": { + "$ref": "#/$defs/ChildCustomizationType", + "description": "Which child customization type this directory holds." + }, + "writable": { + "type": "boolean", + "description": "Whether clients may write into this directory." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "enabled", + "type", + "contents", + "writable" ] }, - "ToolCallPendingResultConfirmationState": { + "AgentCustomization": { "type": "object", - "description": "Tool finished executing, waiting for client to approve the result.", + "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Agent" + }, + "description": { + "type": "string", + "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" - }, - "toolInput": { + "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." + } + }, + "required": [ + "id", + "uri", + "name", + "type" + ] + }, + "SkillCustomization": { + "type": "object", + "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", + "properties": { + "id": { "type": "string", - "description": "Raw tool input" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "name": { + "type": "string", + "description": "Human-readable name." }, - "content": { + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" - }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." - }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } + "$ref": "#/$defs/Icon" }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "description": "Icons for UI display." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "type": { + "$ref": "#/$defs/CustomizationType.Skill" }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "description": { + "type": "string", + "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." + }, + "disableModelInvocation": { + "type": "boolean", + "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "type" ] }, - "ToolCallCompletedState": { + "PromptCustomization": { "type": "object", - "description": "Tool completed successfully or with an error.", + "description": "A prompt contributed by a plugin or directory.", "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" - }, - "displayName": { + "id": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "toolInput": { + "name": { "type": "string", - "description": "Raw tool input" - }, - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" - }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "description": "Human-readable name." }, - "content": { + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" - }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." - }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } + "$ref": "#/$defs/Icon" }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "description": "Icons for UI display." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Completed" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "type": { + "$ref": "#/$defs/CustomizationType.Prompt" }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "description": { + "type": "string", + "description": "Short description of what the prompt does." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "type" ] }, - "ToolCallCancelledState": { + "RuleCustomization": { "type": "object", - "description": "Tool call was cancelled before execution.", + "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "type": { + "$ref": "#/$defs/CustomizationType.Rule" }, - "toolInput": { + "description": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Cancelled" - }, - "reason": { - "$ref": "#/$defs/ToolCallCancellationReason", - "description": "Why the tool was cancelled" - }, - "reasonMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Optional message explaining the cancellation" + "description": "Description of what the rule enforces." }, - "userSuggestion": { - "$ref": "#/$defs/Message", - "description": "What the user suggested doing instead" + "alwaysApply": { + "type": "boolean", + "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "globs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "reason" + "id", + "uri", + "name", + "type" ] }, - "ToolResultTextContent": { + "HookCustomization": { "type": "object", - "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", + "description": "A hook manifest contributed by a plugin or directory.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Text" + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "text": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "The text content" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Hook" } }, "required": [ - "type", - "text" + "id", + "uri", + "name", + "type" ] }, - "ToolResultEmbeddedResourceContent": { + "McpServerCustomization": { "type": "object", - "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", + "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", "properties": { + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + }, + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, "type": { - "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" + "$ref": "#/$defs/CustomizationType.McpServer" }, - "data": { - "type": "string", - "description": "Base64-encoded data" + "enabled": { + "type": "boolean", + "description": "Whether this MCP server is currently enabled." }, - "contentType": { - "type": "string", - "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + "state": { + "$ref": "#/$defs/McpServerState", + "description": "Current lifecycle state of the MCP server." + }, + "channel": { + "$ref": "#/$defs/URI", + "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + }, + "mcpApp": { + "$ref": "#/$defs/McpServerCustomizationApps", + "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." } }, "required": [ + "id", + "uri", + "name", "type", - "data", - "contentType" + "enabled", + "state" ] }, - "ToolResultResourceContent": { + "McpServerCustomizationApps": { "type": "object", - "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", + "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", "properties": { - "uri": { - "$ref": "#/$defs/URI", - "description": "Content URI" - }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" - }, - "contentType": { - "type": "string", - "description": "Content MIME type" - }, - "type": { - "$ref": "#/$defs/ToolResultContentType.Resource" + "capabilities": { + "$ref": "#/$defs/AhpMcpUiHostCapabilities", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." } }, "required": [ - "uri", - "type" + "capabilities" ] }, - "ToolResultFileEditContent": { + "AhpMcpUiHostCapabilities": { "type": "object", - "description": "Describes a file modification performed by a tool.", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", "properties": { - "before": { + "serverTools": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state before the edit. Absent for file creations or for in-place file edits." + "description": "Producer proxies the MCP `tools/*` methods to the upstream server." }, - "after": { + "serverResources": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state after the edit. Absent for file deletions." + "description": "Producer proxies the MCP `resources/*` methods to the upstream server." }, - "diff": { + "logging": { + "type": "object", + "additionalProperties": {}, + "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + }, + "sampling": { "type": "object", "properties": { - "added": { - "type": "number" - }, - "removed": { - "type": "number" + "tools": { + "type": "string" } }, - "description": "Optional diff display metadata" - }, - "type": { - "$ref": "#/$defs/ToolResultContentType.FileEdit" + "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." + } + } + }, + "McpServerStartingState": { + "type": "object", + "description": "Server is registered with the host but has not yet started.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Starting" } }, "required": [ - "type" + "kind" ] }, - "ToolResultTerminalContent": { + "McpServerReadyState": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "Server is running and serving requests.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Terminal" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Terminal URI (subscribable for full terminal state)" - }, - "title": { - "type": "string", - "description": "Display title for the terminal content" + "kind": { + "$ref": "#/$defs/McpServerStatus.Ready" } }, "required": [ - "type", - "resource", - "title" + "kind" ] }, - "ToolResultSubagentContent": { + "McpServerAuthRequiredState": { "type": "object", - "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", + "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Subagent" + "kind": { + "$ref": "#/$defs/McpServerStatus.AuthRequired" }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Subagent session URI (subscribable for full session state)" + "reason": { + "$ref": "#/$defs/McpAuthRequiredReason", + "description": "Why authentication is required." }, - "title": { - "type": "string", - "description": "Display title for the subagent" + "resource": { + "$ref": "#/$defs/ProtectedResourceMetadata", + "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." }, - "agentName": { - "type": "string", - "description": "Internal agent name" + "requiredScopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." }, "description": { "type": "string", - "description": "Human-readable description of the subagent's task" + "description": "Human-readable hint, typically from the OAuth `error_description`." } }, "required": [ - "type", - "resource", - "title" + "kind", + "reason", + "resource" + ] + }, + "McpServerErrorState": { + "type": "object", + "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Error" + }, + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details." + } + }, + "required": [ + "kind", + "error" + ] + }, + "McpServerStoppedState": { + "type": "object", + "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Stopped" + } + }, + "required": [ + "kind" ] }, "TerminalInfo": { @@ -5027,11 +4913,11 @@ }, "FetchTurnsParams": { "type": "object", - "description": "Fetches historical turns for a chat. Used for lazy loading of conversation\nhistory.", + "description": "Fetches historical turns for a session. Used for lazy loading of conversation\nhistory.", "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "Chat URI" + "description": "Session URI" }, "before": { "type": "string", @@ -5073,7 +4959,7 @@ "properties": { "channel": { "$ref": "#/$defs/URI", - "description": "The chat URI the completion is being requested for." + "description": "The session URI the completion is being requested for." }, "kind": { "$ref": "#/$defs/CompletionItemKind", @@ -5137,71 +5023,6 @@ "items" ] }, - "ChatForkSource": { - "type": "object", - "description": "Identifies a source chat and turn to fork from.", - "properties": { - "chat": { - "$ref": "#/$defs/URI", - "description": "URI of the existing chat to fork from" - }, - "turnId": { - "type": "string", - "description": "Turn ID in the source chat; content up to and including this turn's response is copied" - } - }, - "required": [ - "chat", - "turnId" - ] - }, - "CreateChatParams": { - "type": "object", - "description": "Creates a new chat within a session.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "Session URI containing the new chat." - }, - "chat": { - "$ref": "#/$defs/URI", - "description": "Chat URI (client-chosen, e.g. `ahp-chat:/`)." - }, - "initialMessage": { - "$ref": "#/$defs/Message", - "description": "Optional initial message for the new chat." - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override." - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override." - }, - "source": { - "$ref": "#/$defs/ChatForkSource", - "description": "Optional source chat and turn to fork from." - } - }, - "required": [ - "channel", - "chat" - ] - }, - "DisposeChatParams": { - "type": "object", - "description": "Disposes a chat and cleans up server-side resources.", - "properties": { - "channel": { - "$ref": "#/$defs/URI", - "description": "Channel URI this command targets." - } - }, - "required": [ - "channel" - ] - }, "CreateTerminalParams": { "type": "object", "description": "Creates a new terminal on the server.\n\nAfter creation, the client should subscribe to the terminal URI to receive\nstate updates. The server dispatches `root/terminalsChanged` to update the\nroot terminal list.", diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index e845235f..f6c2cc4a 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -120,7 +120,7 @@ }, "workingDirectory": { "$ref": "#/$defs/URI", - "description": "The default working directory URI for this session. Individual chats\nMAY override via {@link ChatSummary.workingDirectory | their own\n`workingDirectory`}; this field acts as the fallback for any chat that\ndoes not." + "description": "The working directory URI for this session" }, "changes": { "$ref": "#/$defs/ChangesSummary", @@ -613,7 +613,7 @@ "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`)" + "description": "The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`)" }, "state": { "oneOf": [ @@ -811,6 +811,24 @@ "values" ] }, + "PendingMessage": { + "type": "object", + "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this pending message" + }, + "message": { + "$ref": "#/$defs/Message", + "description": "The message that will start the next turn" + } + }, + "required": [ + "id", + "message" + ] + }, "SessionState": { "type": "object", "description": "Full state for a single session, loaded when a client subscribes to the session's URI.", @@ -838,16 +856,34 @@ "$ref": "#/$defs/SessionActiveClient", "description": "The client currently providing tools and interactive capabilities to this session" }, - "chats": { + "turns": { "type": "array", "items": { - "$ref": "#/$defs/ChatSummary" + "$ref": "#/$defs/Turn" }, - "description": "Catalog of chats in this session." + "description": "Completed turns" }, - "defaultChat": { - "$ref": "#/$defs/URI", - "description": "The chat that receives input when the user addresses the session without\nselecting a specific chat. This is a UI routing hint, not a hierarchy\nmarker — chats remain equal peers at the protocol level. Hosts MAY change\nthis over the session's lifetime." + "activeTurn": { + "$ref": "#/$defs/ActiveTurn", + "description": "Currently in-progress turn" + }, + "steeringMessage": { + "$ref": "#/$defs/PendingMessage", + "description": "Message to inject into the current turn at a convenient point" + }, + "queuedMessages": { + "type": "array", + "items": { + "$ref": "#/$defs/PendingMessage" + }, + "description": "Messages to send automatically as new turns after the current turn finishes" + }, + "inputRequests": { + "type": "array", + "items": { + "$ref": "#/$defs/SessionInputRequest" + }, + "description": "Requests for user input that are currently blocking or informing session progress" }, "config": { "$ref": "#/$defs/SessionConfigState", @@ -876,7 +912,7 @@ "required": [ "summary", "lifecycle", - "chats" + "turns" ] }, "SessionActiveClient": { @@ -931,7 +967,6 @@ }, "SessionSummary": { "type": "object", - "description": "Lightweight catalog entry summarizing one session. Surfaced via\n{@link RootChannelCommands.listSessions | `root/listSessions`} and\n`root/sessionAdded`/`root/sessionSummaryChanged` notifications.\n\n**Aggregation across chats.** Once a session contains more than one chat,\nseveral `SessionSummary` fields are derived from the underlying\n{@link SessionState.chats | chat catalog}. Producers SHOULD follow these\nrules so clients that only consume the session summary (e.g. a session\nlist) still see meaningful state:\n\n- `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` /\n `Error` — bits 0–4) from the\n {@link SessionState.defaultChat | default chat} when present, else from\n the most recently modified chat. **Promote** `InputNeeded` whenever any\n chat in the session needs input, and **promote** `Error` whenever any\n chat is in an error state — both override the default-chat bits. The\n orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped.\n- `activity`: mirror the activity string of the default chat, or of the\n chat currently driving the promoted status bits when a non-default chat\n wins (e.g. the chat that raised `InputNeeded`).\n- `modifiedAt`: the max of all chats' `modifiedAt`.\n- `model` / `agent`: the session-level selection. Per-chat overrides are\n surfaced on individual {@link ChatSummary} entries, not aggregated up.\n- `workingDirectory`: the session-level **default**. Individual chats MAY\n override via {@link ChatSummary.workingDirectory}; aggregating these up\n is meaningless and SHOULD NOT be attempted.\n- `changes`: optional roll-up across all chats. Producers MAY sum the\n per-chat changeset stats or report the most expensive chat's stats —\n whichever is cheaper for the host to compute.\n\nSessions with a single chat trivially satisfy all of the above (the chat's\nvalues pass through unchanged). The rules only matter once a session\ncarries multiple chats.", "properties": { "resource": { "$ref": "#/$defs/URI", @@ -975,7 +1010,7 @@ }, "workingDirectory": { "$ref": "#/$defs/URI", - "description": "The default working directory URI for this session. Individual chats\nMAY override via {@link ChatSummary.workingDirectory | their own\n`workingDirectory`}; this field acts as the fallback for any chat that\ndoes not." + "description": "The working directory URI for this session" }, "changes": { "$ref": "#/$defs/ChangesSummary", @@ -1159,1774 +1194,1570 @@ "values" ] }, - "ToolDefinition": { + "SessionInputOption": { "type": "object", - "description": "Describes a tool available in a session, provided by either the server or the active client.", + "description": "A choice in a select-style question.", "properties": { - "name": { + "id": { "type": "string", - "description": "Unique tool identifier" + "description": "Stable option identifier; for MCP enum values this is the enum string" }, - "title": { + "label": { "type": "string", - "description": "Human-readable display name" + "description": "Display label" }, "description": { "type": "string", - "description": "Description of what the tool does" - }, - "inputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." - }, - "outputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." - }, - "annotations": { - "$ref": "#/$defs/ToolAnnotations", - "description": "Behavioral hints about the tool. All properties are advisory." + "description": "Optional secondary text" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." + "recommended": { + "type": "boolean", + "description": "Whether this option is the recommended/default choice" } }, "required": [ - "name" + "id", + "label" ] }, - "ToolAnnotations": { + "SessionInputQuestionBase": { "type": "object", - "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "title": { + "id": { "type": "string", - "description": "Alternate human-readable title" - }, - "readOnlyHint": { - "type": "boolean", - "description": "Tool does not modify its environment (default: false)" + "description": "Stable question identifier used as the key in `answers`" }, - "destructiveHint": { - "type": "boolean", - "description": "Tool may perform destructive updates (default: true)" + "title": { + "type": "string", + "description": "Short display title" }, - "idempotentHint": { - "type": "boolean", - "description": "Repeated calls with the same arguments have no additional effect (default: false)" + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "openWorldHint": { + "required": { "type": "boolean", - "description": "Tool may interact with external entities (default: true)" + "description": "Whether the user must answer this question to accept the request" } - } + }, + "required": [ + "id", + "message" + ] }, - "CustomizationBase": { + "SessionInputTextQuestion": { "type": "object", - "description": "Fields shared by every customization variant.", + "description": "Text question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "Stable question identifier used as the key in `answers`" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "title": { + "type": "string", + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Prompt shown to the user" }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.Text" + }, + "format": { + "type": "string", + "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + }, + "min": { + "type": "number", + "description": "Minimum string length" + }, + "max": { + "type": "number", + "description": "Maximum string length" + }, + "defaultValue": { + "type": "string", + "description": "Default text" } }, "required": [ "id", - "uri", - "name" + "message", + "kind" ] }, - "CustomizationLoadingState": { + "SessionInputNumberQuestion": { "type": "object", - "description": "Container is being loaded by the host.", + "description": "Numeric question within a session input request.", "properties": { + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" + }, + "message": { + "type": "string", + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loading" + "oneOf": [ + { + "$ref": "#/$defs/SessionInputQuestionKind.Number" + }, + { + "$ref": "#/$defs/SessionInputQuestionKind.Integer" + } + ] + }, + "min": { + "type": "number", + "description": "Minimum value" + }, + "max": { + "type": "number", + "description": "Maximum value" + }, + "defaultValue": { + "type": "number", + "description": "Default numeric value" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationLoadedState": { + "SessionInputBooleanQuestion": { "type": "object", - "description": "Container loaded successfully.", + "description": "Boolean question within a session input request.", "properties": { + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" + }, + "message": { + "type": "string", + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loaded" + "$ref": "#/$defs/SessionInputQuestionKind.Boolean" + }, + "defaultValue": { + "type": "boolean", + "description": "Default boolean value" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationDegradedState": { + "SessionInputSingleSelectQuestion": { "type": "object", - "description": "Container partially loaded but has warnings.", + "description": "Single-select question within a session input request.", "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Degraded" - }, - "message": { + "id": { "type": "string", - "description": "Human-readable description of the warning." - } - }, - "required": [ - "kind", - "message" - ] - }, - "CustomizationErrorState": { - "type": "object", - "description": "Container failed to load.", - "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Error" + "description": "Stable question identifier used as the key in `answers`" }, - "message": { - "type": "string", - "description": "Human-readable error message." - } - }, - "required": [ - "kind", - "message" - ] - }, - "ContainerCustomizationBase": { - "type": "object", - "description": "Fields shared by container customizations.", - "properties": { - "id": { + "title": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.SingleSelect" }, - "children": { + "options": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Options the user may select from" + }, + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text instead of selecting an option" } }, "required": [ "id", - "uri", - "name", - "enabled" + "message", + "kind", + "options" ] }, - "PluginCustomization": { + "SessionInputMultiSelectQuestion": { "type": "object", - "description": "An [Open Plugins](https://open-plugins.com/) plugin.", + "description": "Multi-select question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable question identifier used as the key in `answers`" }, - "name": { + "title": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Short display title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.MultiSelect" }, - "children": { + "options": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Options the user may select from" }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text in addition to selecting options" + }, + "min": { + "type": "number", + "description": "Minimum selected item count" + }, + "max": { + "type": "number", + "description": "Maximum selected item count" } }, "required": [ "id", - "uri", - "name", - "enabled", - "type" + "message", + "kind", + "options" ] }, - "ClientPluginCustomization": { + "SessionInputRequest": { "type": "object", - "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", + "description": "A live request for user input.\n\nThe server creates or replaces requests with `session/inputRequested`.\nClients sync drafts with `session/inputAnswerChanged` and complete requests\nwith `session/inputCompleted`.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable request identifier" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Display message for the request as a whole" }, - "icons": { + "url": { + "$ref": "#/$defs/URI", + "description": "URL the user should review or open, for URL-style elicitations" + }, + "questions": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/SessionInputQuestion" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." - }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "description": "Ordered questions to ask the user" }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" + "answers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/SessionInputAnswer" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Current draft or submitted answers, keyed by question ID" + } + }, + "required": [ + "id" + ] + }, + "SessionInputTextAnswerValue": { + "type": "object", + "description": "Value captured for one answer.", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Text" }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputNumberAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Number" }, - "nonce": { - "type": "string", - "description": "Opaque version token used by the host to detect changes." + "value": { + "type": "number" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type" + "kind", + "value" ] }, - "DirectoryCustomization": { + "SessionInputBooleanAnswerValue": { "type": "object", - "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Boolean" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "value": { + "type": "boolean" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputSelectedAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Selected" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "string" }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." - }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." - }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" - }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Directory" - }, - "contents": { - "$ref": "#/$defs/ChildCustomizationType", - "description": "Which child customization type this directory holds." - }, - "writable": { - "type": "boolean", - "description": "Whether clients may write into this directory." + "description": "Free-form text entered instead of selecting an option" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type", - "contents", - "writable" + "kind", + "value" ] }, - "AgentCustomization": { + "SessionInputSelectedManyAnswerValue": { "type": "object", - "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.SelectedMany" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "array", + "items": { + "type": "string" + } }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Agent" - }, - "description": { - "type": "string", - "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." + "description": "Free-form text entered in addition to selected options" } }, "required": [ - "id", - "uri", - "name", - "type" + "kind", + "value" ] }, - "SkillCustomization": { + "SessionInputAnswered": { "type": "object", - "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "state": { + "oneOf": [ + { + "$ref": "#/$defs/SessionInputAnswerState.Draft" + }, + { + "$ref": "#/$defs/SessionInputAnswerState.Submitted" + } + ], + "description": "Answer state" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "$ref": "#/$defs/SessionInputAnswerValue", + "description": "Answer value" + } + }, + "required": [ + "state", + "value" + ] + }, + "SessionInputSkipped": { + "type": "object", + "properties": { + "state": { + "$ref": "#/$defs/SessionInputAnswerState.Skipped", + "description": "Answer state" }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Skill" - }, - "description": { - "type": "string", - "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." - }, - "disableModelInvocation": { - "type": "boolean", - "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." + "description": "Free-form reason or value captured while skipping, if any" } }, "required": [ - "id", - "uri", - "name", - "type" + "state" ] }, - "PromptCustomization": { + "Turn": { "type": "object", - "description": "A prompt contributed by a plugin or directory.", + "description": "A completed request/response cycle.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" }, - "type": { - "$ref": "#/$defs/CustomizationType.Prompt" + "state": { + "$ref": "#/$defs/TurnState", + "description": "How the turn ended" }, - "description": { - "type": "string", - "description": "Short description of what the prompt does." + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details if state is `'error'`" } }, "required": [ "id", - "uri", - "name", - "type" + "message", + "responseParts", + "usage", + "state" ] }, - "RuleCustomization": { + "ActiveTurn": { "type": "object", - "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", + "description": "An in-progress turn — the assistant is actively streaming.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Rule" - }, - "description": { - "type": "string", - "description": "Description of what the rule enforces." - }, - "alwaysApply": { - "type": "boolean", - "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." }, - "globs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" } }, "required": [ "id", - "uri", - "name", - "type" + "message", + "responseParts", + "usage" ] }, - "HookCustomization": { + "Message": { "type": "object", - "description": "A hook manifest contributed by a plugin or directory.", + "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", "properties": { - "id": { + "text": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Message text" }, - "name": { - "type": "string", - "description": "Human-readable name." + "origin": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + }, + "required": [ + "kind" + ], + "description": "The origin of the message" }, - "icons": { + "attachments": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/MessageAttachment" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "File/selection attachments" }, - "type": { - "$ref": "#/$defs/CustomizationType.Hook" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." } }, "required": [ - "id", - "uri", - "name", - "type" + "text", + "origin" ] }, - "McpServerCustomization": { + "MessageAttachmentBase": { "type": "object", - "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", + "description": "Common fields shared by all {@link MessageAttachment} variants.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." - }, - "name": { + "label": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.McpServer" - }, - "enabled": { - "type": "boolean", - "description": "Whether this MCP server is currently enabled." - }, - "state": { - "$ref": "#/$defs/McpServerState", - "description": "Current lifecycle state of the MCP server." + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "channel": { - "$ref": "#/$defs/URI", - "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "mcpApp": { - "$ref": "#/$defs/McpServerCustomizationApps", - "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." } }, "required": [ - "id", - "uri", - "name", - "type", - "enabled", - "state" + "label" ] }, - "McpServerCustomizationApps": { + "SimpleMessageAttachment": { "type": "object", - "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", + "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", "properties": { - "capabilities": { - "$ref": "#/$defs/AhpMcpUiHostCapabilities", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Simple", + "description": "Discriminant" + }, + "modelRepresentation": { + "type": "string", + "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." } }, "required": [ - "capabilities" + "label", + "type" ] }, - "AhpMcpUiHostCapabilities": { + "MessageEmbeddedResourceAttachment": { "type": "object", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", + "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", "properties": { - "serverTools": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `tools/*` methods to the upstream server." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "serverResources": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `resources/*` methods to the upstream server." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "logging": { + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { "type": "object", "additionalProperties": {}, - "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, - "sampling": { - "type": "object", - "properties": { - "tools": { - "type": "string" - } - }, - "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." - } - } - }, - "McpServerStartingState": { - "type": "object", - "description": "Server is registered with the host but has not yet started.", - "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.Starting" + "type": { + "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", + "description": "Discriminant" + }, + "data": { + "type": "string", + "description": "Base64-encoded binary data" + }, + "contentType": { + "type": "string", + "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + }, + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "kind" + "label", + "type", + "data", + "contentType" ] }, - "McpServerReadyState": { + "MessageResourceAttachment": { "type": "object", - "description": "Server is running and serving requests.", + "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.Ready" + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Resource", + "description": "Discriminant" + }, + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "kind" + "label", + "uri", + "type" ] }, - "McpServerAuthRequiredState": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.AuthRequired" - }, - "reason": { - "$ref": "#/$defs/McpAuthRequiredReason", - "description": "Why authentication is required." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Annotations", + "description": "Discriminant" }, "resource": { - "$ref": "#/$defs/ProtectedResourceMetadata", - "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." + "$ref": "#/$defs/URI", + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "requiredScopes": { + "annotationIds": { "type": "array", "items": { "type": "string" }, - "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." - }, - "description": { - "type": "string", - "description": "Human-readable hint, typically from the OAuth `error_description`." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ - "kind", - "reason", + "label", + "type", "resource" ] }, - "McpServerErrorState": { + "MarkdownResponsePart": { "type": "object", - "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Error" + "$ref": "#/$defs/ResponsePartKind.Markdown", + "description": "Discriminant" }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details." + "id": { + "type": "string", + "description": "Part identifier, used by `session/delta` to target this part for content appends" + }, + "content": { + "type": "string", + "description": "Markdown content" } }, "required": [ "kind", - "error" + "id", + "content" ] }, - "McpServerStoppedState": { + "ResourceReponsePart": { "type": "object", - "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", + "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" + }, "kind": { - "$ref": "#/$defs/McpServerStatus.Stopped" + "$ref": "#/$defs/ResponsePartKind.ContentRef", + "description": "Discriminant" } }, "required": [ + "uri", "kind" ] }, - "ChatState": { + "ToolCallResponsePart": { "type": "object", - "description": "Full state for a single chat, loaded when a client subscribes to the chat's\nURI.\n\nThe lightweight catalog representation of a chat is {@link ChatSummary},\ncarried in {@link SessionState.chats | `SessionState.chats`}. `ChatState`\n**denormalizes** every {@link ChatSummary} field directly onto itself so\nsubscribers receive one flat object instead of having to merge a nested\n`summary` sub-object. Producers MUST keep the two representations\nconsistent: any change to the inlined fields below SHOULD also be\nannounced on the parent session via the matching\n{@link SessionChatUpdatedAction | `session/chatUpdated`} action.", + "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { - "type": "string", - "description": "Human-readable description of what the chat is currently doing" - }, - "modifiedAt": { - "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nHosts MAY override this for individual chats — for example, to give a\nsubordinate chat its own git worktree so multiple chats in a session can\nmake independent edits that the orchestrator later merges back." - }, - "turns": { - "type": "array", - "items": { - "$ref": "#/$defs/Turn" - }, - "description": "Completed turns" - }, - "activeTurn": { - "$ref": "#/$defs/ActiveTurn", - "description": "Currently in-progress turn" - }, - "steeringMessage": { - "$ref": "#/$defs/PendingMessage", - "description": "Message to inject into the current turn at a convenient point" - }, - "queuedMessages": { - "type": "array", - "items": { - "$ref": "#/$defs/PendingMessage" - }, - "description": "Messages to send automatically as new turns after the current turn finishes" - }, - "inputRequests": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputRequest" - }, - "description": "Requests for user input that are currently blocking or informing chat progress" + "kind": { + "$ref": "#/$defs/ResponsePartKind.ToolCall", + "description": "Discriminant" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this chat." + "toolCall": { + "$ref": "#/$defs/ToolCallState", + "description": "Full tool call lifecycle state" } }, "required": [ - "resource", - "title", - "status", - "modifiedAt", - "turns" + "kind", + "toolCall" ] }, - "ChatSummary": { + "ReasoningResponsePart": { "type": "object", - "description": "Lightweight catalog entry for a chat, carried in\n{@link SessionState.chats | `SessionState.chats`}. The full conversation\nlives in {@link ChatState}, which inlines (denormalizes) every field below.", + "description": "Reasoning/thinking content from the model.", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" + "kind": { + "$ref": "#/$defs/ResponsePartKind.Reasoning", + "description": "Discriminant" }, - "activity": { + "id": { "type": "string", - "description": "Human-readable description of what the chat is currently doing" + "description": "Part identifier, used by `session/reasoning` to target this part for content appends" }, - "modifiedAt": { + "content": { "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nSee {@link ChatState.workingDirectory} for usage notes." + "description": "Accumulated reasoning text" } }, "required": [ - "resource", - "title", - "status", - "modifiedAt" + "kind", + "id", + "content" ] }, - "PendingMessage": { + "SystemNotificationResponsePart": { "type": "object", - "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", + "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", "properties": { - "id": { - "type": "string", - "description": "Unique identifier for this pending message" + "kind": { + "$ref": "#/$defs/ResponsePartKind.SystemNotification", + "description": "Discriminant" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that will start the next turn" + "content": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "The text of the system notification" } }, "required": [ - "id", - "message" + "kind", + "content" ] }, - "ChatInputOption": { + "ConfirmationOption": { "type": "object", - "description": "A choice in a select-style question.", + "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", "properties": { "id": { "type": "string", - "description": "Stable option identifier; for MCP enum values this is the enum string" + "description": "Unique identifier for the option, returned in the confirmed action" }, "label": { "type": "string", - "description": "Display label" + "description": "Human-readable label displayed to the user" }, - "description": { - "type": "string", - "description": "Optional secondary text" + "kind": { + "$ref": "#/$defs/ConfirmationOptionKind", + "description": "Whether this option represents an approval or denial" }, - "recommended": { - "type": "boolean", - "description": "Whether this option is the recommended/default choice" + "group": { + "type": "number", + "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." } }, "required": [ "id", - "label" + "label", + "kind" ] }, - "ChatInputQuestionBase": { + "ToolCallClientContributor": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.Client" }, - "title": { + "clientId": { "type": "string", - "description": "Short display title" + "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `session/toolCallComplete` with the result." + } + }, + "required": [ + "kind", + "clientId" + ] + }, + "ToolCallMcpContributor": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.MCP" }, - "message": { + "customizationId": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." } }, "required": [ - "id", - "message" + "kind", + "customizationId" ] }, - "ChatInputTextQuestion": { + "ToolCallBase": { "type": "object", - "description": "Text question within a chat input request.", + "description": "Metadata common to all tool call states.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" + "description": "Unique tool call identifier" }, - "message": { + "toolName": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" - }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Text" + "description": "Internal tool name (for debugging/logging)" }, - "format": { + "displayName": { "type": "string", - "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + "description": "Human-readable tool name" }, - "min": { - "type": "number", - "description": "Minimum string length" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "max": { - "type": "number", - "description": "Maximum string length" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + } + }, + "required": [ + "toolCallId", + "toolName", + "displayName" + ] + }, + "ToolCallParameterFields": { + "type": "object", + "description": "Properties available once tool call parameters are fully received.", + "properties": { + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "defaultValue": { + "toolInput": { "type": "string", - "description": "Default text" + "description": "Raw tool input" } }, "required": [ - "id", - "message", - "kind" + "invocationMessage" ] }, - "ChatInputNumberQuestion": { + "ToolCallResult": { "type": "object", - "description": "Numeric question within a chat input request.", + "description": "Tool execution result details, available after execution completes.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" }, - "title": { - "type": "string", - "description": "Short display title" + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" }, - "message": { - "type": "string", - "description": "Prompt shown to the user" + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." }, - "kind": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputQuestionKind.Number" + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" }, - { - "$ref": "#/$defs/ChatInputQuestionKind.Integer" + "code": { + "type": "string" } - ] - }, - "min": { - "type": "number", - "description": "Minimum value" - }, - "max": { - "type": "number", - "description": "Maximum value" - }, - "defaultValue": { - "type": "number", - "description": "Default numeric value" + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" } }, "required": [ - "id", - "message", - "kind" + "success", + "pastTenseMessage" ] }, - "ChatInputBooleanQuestion": { + "ToolCallStreamingState": { "type": "object", - "description": "Boolean question within a chat input request.", + "description": "LM is streaming the tool call parameters.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Boolean" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "defaultValue": { - "type": "boolean", - "description": "Default boolean value" + "status": { + "$ref": "#/$defs/ToolCallStatus.Streaming" + }, + "partialInput": { + "type": "string", + "description": "Partial parameters accumulated so far" + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Progress message shown while parameters are streaming" } }, "required": [ - "id", - "message", - "kind" + "toolCallId", + "toolName", + "displayName", + "status" ] }, - "ChatInputSingleSelectQuestion": { + "ToolCallPendingConfirmationState": { "type": "object", - "description": "Single-select question within a chat input request.", + "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.SingleSelect" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" + }, + "confirmationTitle": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" + }, + "edits": { + "type": "object", + "properties": { + "items": { + "type": "string" + } + }, + "required": [ + "items" + ], + "description": "File edits that this tool call will perform, for preview before confirmation" + }, + "editable": { + "type": "boolean", + "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" }, "options": { "type": "array", "items": { - "$ref": "#/$defs/ChatInputOption" + "$ref": "#/$defs/ConfirmationOption" }, - "description": "Options the user may select from" - }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text instead of selecting an option" + "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." } }, "required": [ - "id", - "message", - "kind", - "options" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status" ] }, - "ChatInputMultiSelectQuestion": { + "ToolCallRunningState": { "type": "object", - "description": "Multi-select question within a chat input request.", + "description": "Tool is actively executing.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" + "description": "Human-readable tool name" }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.MultiSelect" - }, - "options": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputOption" - }, - "description": "Options the user may select from" - }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text in addition to selecting options" - }, - "min": { - "type": "number", - "description": "Minimum selected item count" - }, - "max": { - "type": "number", - "description": "Maximum selected item count" - } - }, - "required": [ - "id", - "message", - "kind", - "options" - ] - }, - "ChatInputRequest": { - "type": "object", - "description": "A live request for user input.\n\nThe server creates or replaces requests with `chat/inputRequested`.\nClients sync drafts with `chat/inputAnswerChanged` and complete requests\nwith `chat/inputCompleted`.", - "properties": { - "id": { - "type": "string", - "description": "Stable request identifier" - }, - "message": { - "type": "string", - "description": "Display message for the request as a whole" - }, - "url": { - "$ref": "#/$defs/URI", - "description": "URL the user should review or open, for URL-style elicitations" - }, - "questions": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputQuestion" - }, - "description": "Ordered questions to ask the user" - }, - "answers": { + "_meta": { "type": "object", - "additionalProperties": { - "$ref": "#/$defs/ChatInputAnswer" - }, - "description": "Current draft or submitted answers, keyed by question ID" - } - }, - "required": [ - "id" - ] - }, - "ChatInputTextAnswerValue": { - "type": "object", - "description": "Value captured for one answer.", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Text" - }, - "value": { - "type": "string" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputNumberAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Number" + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "value": { - "type": "number" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputBooleanAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Boolean" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "value": { - "type": "boolean" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Selected" + "toolInput": { + "type": "string", + "description": "Raw tool input" }, - "value": { - "type": "string" + "status": { + "$ref": "#/$defs/ToolCallStatus.Running" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form text entered instead of selecting an option" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedManyAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.SelectedMany" + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" }, - "value": { - "type": "array", - "items": { - "type": "string" - } + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" }, - "freeformValues": { + "content": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/ToolResultContent" }, - "description": "Free-form text entered in addition to selected options" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputAnswered": { - "type": "object", - "properties": { - "state": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputAnswerState.Draft" - }, - { - "$ref": "#/$defs/ChatInputAnswerState.Submitted" - } - ], - "description": "Answer state" - }, - "value": { - "$ref": "#/$defs/ChatInputAnswerValue", - "description": "Answer value" + "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." } }, "required": [ - "state", - "value" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "confirmed" ] }, - "ChatInputSkipped": { + "ToolCallPendingResultConfirmationState": { "type": "object", + "description": "Tool finished executing, waiting for client to approve the result.", "properties": { - "state": { - "$ref": "#/$defs/ChatInputAnswerState.Skipped", - "description": "Answer state" + "toolCallId": { + "type": "string", + "description": "Unique tool call identifier" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form reason or value captured while skipping, if any" - } - }, - "required": [ - "state" - ] - }, - "Turn": { - "type": "object", - "description": "A completed request/response cycle.", - "properties": { - "id": { + "toolName": { "type": "string", - "description": "Turn identifier" + "description": "Internal tool name (for debugging/logging)" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "displayName": { + "type": "string", + "description": "Human-readable tool name" }, - "responseParts": { - "type": "array", - "items": { - "$ref": "#/$defs/ResponsePart" - }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "state": { - "$ref": "#/$defs/TurnState", - "description": "How the turn ended" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details if state is `'error'`" - } - }, - "required": [ - "id", - "message", - "responseParts", - "usage", - "state" - ] - }, - "ActiveTurn": { - "type": "object", - "description": "An in-progress turn — the assistant is actively streaming.", - "properties": { - "id": { + "toolInput": { "type": "string", - "description": "Turn identifier" - }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "description": "Raw tool input" }, - "responseParts": { - "type": "array", - "items": { - "$ref": "#/$defs/ResponsePart" - }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" - } - }, - "required": [ - "id", - "message", - "responseParts", - "usage" - ] - }, - "Message": { - "type": "object", - "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", - "properties": { - "text": { - "type": "string", - "description": "Message text" + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" }, - "origin": { + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + }, + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + }, + "error": { "type": "object", "properties": { - "kind": { + "message": { + "type": "string" + }, + "code": { "type": "string" } }, "required": [ - "kind" + "message" ], - "description": "The origin of the message" + "description": "Error details if the tool failed" }, - "attachments": { - "type": "array", - "items": { - "$ref": "#/$defs/MessageAttachment" - }, - "description": "File/selection attachments" + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "text", - "origin" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "MessageAttachmentBase": { + "ToolCallCompletedState": { "type": "object", - "description": "Common fields shared by all {@link MessageAttachment} variants.", + "description": "Tool completed successfully or with an error.", "properties": { - "label": { + "toolCallId": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool call identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "displayKind": { + "displayName": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" + }, + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + }, + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + }, + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" + }, + "status": { + "$ref": "#/$defs/ToolCallStatus.Completed" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "label" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "SimpleMessageAttachment": { + "ToolCallCancelledState": { "type": "object", - "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", + "description": "Tool call was cancelled before execution.", "properties": { - "label": { + "toolCallId": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool call identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "displayKind": { + "displayName": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Simple", - "description": "Discriminant" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "modelRepresentation": { + "toolInput": { "type": "string", - "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." + "description": "Raw tool input" + }, + "status": { + "$ref": "#/$defs/ToolCallStatus.Cancelled" + }, + "reason": { + "$ref": "#/$defs/ToolCallCancellationReason", + "description": "Why the tool was cancelled" + }, + "reasonMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Optional message explaining the cancellation" + }, + "userSuggestion": { + "$ref": "#/$defs/Message", + "description": "What the user suggested doing instead" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "label", - "type" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "reason" ] }, - "MessageEmbeddedResourceAttachment": { + "ToolDefinition": { "type": "object", - "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", + "description": "Describes a tool available in a session, provided by either the server or the active client.", "properties": { - "label": { + "name": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "title": { + "type": "string", + "description": "Human-readable display name" + }, + "description": { + "type": "string", + "description": "Description of what the tool does" + }, + "inputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } + }, + "required": [ + "type" + ], + "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." + }, + "outputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } + }, + "required": [ + "type" + ], + "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "annotations": { + "$ref": "#/$defs/ToolAnnotations", + "description": "Behavioral hints about the tool. All properties are advisory." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", - "description": "Discriminant" - }, - "data": { - "type": "string", - "description": "Base64-encoded binary data" - }, - "contentType": { - "type": "string", - "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" - }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." + "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." } }, "required": [ - "label", - "type", - "data", - "contentType" + "name" ] }, - "MessageResourceAttachment": { + "ToolAnnotations": { "type": "object", - "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", + "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." - }, - "displayKind": { + "title": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Content URI" + "description": "Alternate human-readable title" }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" + "readOnlyHint": { + "type": "boolean", + "description": "Tool does not modify its environment (default: false)" }, - "contentType": { - "type": "string", - "description": "Content MIME type" + "destructiveHint": { + "type": "boolean", + "description": "Tool may perform destructive updates (default: true)" }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Resource", - "description": "Discriminant" + "idempotentHint": { + "type": "boolean", + "description": "Repeated calls with the same arguments have no additional effect (default: false)" }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." + "openWorldHint": { + "type": "boolean", + "description": "Tool may interact with external entities (default: true)" } - }, - "required": [ - "label", - "uri", - "type" - ] + } }, - "MessageAnnotationsAttachment": { + "ToolResultTextContent": { "type": "object", - "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", + "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." - }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, "type": { - "$ref": "#/$defs/MessageAttachmentKind.Annotations", - "description": "Discriminant" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." + "$ref": "#/$defs/ToolResultContentType.Text" }, - "annotationIds": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." + "text": { + "type": "string", + "description": "The text content" } }, "required": [ - "label", "type", - "resource" + "text" ] }, - "MarkdownResponsePart": { + "ToolResultEmbeddedResourceContent": { "type": "object", + "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Markdown", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" }, - "id": { + "data": { "type": "string", - "description": "Part identifier, used by `chat/delta` to target this part for content appends" + "description": "Base64-encoded data" }, - "content": { + "contentType": { "type": "string", - "description": "Markdown content" + "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" } }, "required": [ - "kind", - "id", - "content" + "type", + "data", + "contentType" ] }, - "ResourceReponsePart": { + "ToolResultResourceContent": { "type": "object", - "description": "A content part that's a reference to large content stored outside the state tree.", + "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", "properties": { "uri": { "$ref": "#/$defs/URI", @@ -2940,819 +2771,874 @@ "type": "string", "description": "Content MIME type" }, - "kind": { - "$ref": "#/$defs/ResponsePartKind.ContentRef", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Resource" } }, "required": [ "uri", - "kind" + "type" ] }, - "ToolCallResponsePart": { + "ToolResultFileEditContent": { "type": "object", - "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", + "description": "Describes a file modification performed by a tool.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.ToolCall", - "description": "Discriminant" + "before": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state before the edit. Absent for file creations or for in-place file edits." }, - "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "Full tool call lifecycle state" + "after": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state after the edit. Absent for file deletions." + }, + "diff": { + "type": "object", + "properties": { + "added": { + "type": "number" + }, + "removed": { + "type": "number" + } + }, + "description": "Optional diff display metadata" + }, + "type": { + "$ref": "#/$defs/ToolResultContentType.FileEdit" } }, "required": [ - "kind", - "toolCall" + "type" ] }, - "ReasoningResponsePart": { + "ToolResultTerminalContent": { "type": "object", - "description": "Reasoning/thinking content from the model.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Reasoning", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Terminal" }, - "id": { - "type": "string", - "description": "Part identifier, used by `chat/reasoning` to target this part for content appends" + "resource": { + "$ref": "#/$defs/URI", + "description": "Terminal URI (subscribable for full terminal state)" }, - "content": { + "title": { "type": "string", - "description": "Accumulated reasoning text" + "description": "Display title for the terminal content" } }, "required": [ - "kind", - "id", - "content" + "type", + "resource", + "title" ] }, - "SystemNotificationResponsePart": { + "ToolResultSubagentContent": { "type": "object", - "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", + "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.SystemNotification", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Subagent" + }, + "resource": { + "$ref": "#/$defs/URI", + "description": "Subagent session URI (subscribable for full session state)" + }, + "title": { + "type": "string", + "description": "Display title for the subagent" }, - "content": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "The text of the system notification" + "agentName": { + "type": "string", + "description": "Internal agent name" + }, + "description": { + "type": "string", + "description": "Human-readable description of the subagent's task" } }, "required": [ - "kind", - "content" + "type", + "resource", + "title" ] }, - "ConfirmationOption": { + "CustomizationBase": { "type": "object", - "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", + "description": "Fields shared by every customization variant.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the option, returned in the confirmed action" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "label": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Human-readable label displayed to the user" + "description": "Human-readable name." }, - "kind": { - "$ref": "#/$defs/ConfirmationOptionKind", - "description": "Whether this option represents an approval or denial" + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "group": { - "type": "number", - "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." } }, "required": [ "id", - "label", - "kind" + "uri", + "name" ] }, - "ToolCallClientContributor": { + "CustomizationLoadingState": { "type": "object", + "description": "Container is being loaded by the host.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.Client" - }, - "clientId": { - "type": "string", - "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `chat/toolCallComplete` with the result." + "$ref": "#/$defs/CustomizationLoadStatus.Loading" } }, "required": [ - "kind", - "clientId" + "kind" ] }, - "ToolCallMcpContributor": { + "CustomizationLoadedState": { "type": "object", + "description": "Container loaded successfully.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.MCP" - }, - "customizationId": { - "type": "string", - "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." + "$ref": "#/$defs/CustomizationLoadStatus.Loaded" } }, "required": [ - "kind", - "customizationId" + "kind" ] }, - "ToolCallBase": { + "CustomizationDegradedState": { "type": "object", - "description": "Metadata common to all tool call states.", + "description": "Container partially loaded but has warnings.", "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Degraded" }, - "displayName": { + "message": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "description": "Human-readable description of the warning." } }, "required": [ - "toolCallId", - "toolName", - "displayName" + "kind", + "message" ] }, - "ToolCallParameterFields": { + "CustomizationErrorState": { "type": "object", - "description": "Properties available once tool call parameters are fully received.", + "description": "Container failed to load.", "properties": { - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Error" }, - "toolInput": { + "message": { "type": "string", - "description": "Raw tool input" + "description": "Human-readable error message." } }, "required": [ - "invocationMessage" + "kind", + "message" ] }, - "ToolCallResult": { + "ContainerCustomizationBase": { "type": "object", - "description": "Tool execution result details, available after execution completes.", + "description": "Fields shared by container customizations.", "properties": { - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "content": { + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } - }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" - } - }, - "required": [ - "success", - "pastTenseMessage" - ] - }, - "ToolCallStreamingState": { - "type": "object", - "description": "LM is streaming the tool call parameters.", - "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "displayName": { + "clientId": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Streaming" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "partialInput": { - "type": "string", - "description": "Partial parameters accumulated so far" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Progress message shown while parameters are streaming" + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/ChildCustomization" + }, + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "status" + "id", + "uri", + "name", + "enabled" ] }, - "ToolCallPendingConfirmationState": { - "type": "object", - "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", - "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" - }, - "displayName": { - "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" - }, - "toolInput": { + "PluginCustomization": { + "type": "object", + "description": "An [Open Plugins](https://open-plugins.com/) plugin.", + "properties": { + "id": { "type": "string", - "description": "Raw tool input" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "confirmationTitle": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" + "name": { + "type": "string", + "description": "Human-readable name." }, - "edits": { - "type": "object", - "properties": { - "items": { - "type": "string" - } + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" }, - "required": [ - "items" - ], - "description": "File edits that this tool call will perform, for preview before confirmation" + "description": "Icons for UI display." }, - "editable": { + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "enabled": { "type": "boolean", - "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" + "description": "Whether this container is currently enabled." }, - "options": { + "clientId": { + "type": "string", + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + }, + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + }, + "children": { "type": "array", "items": { - "$ref": "#/$defs/ConfirmationOption" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallRunningState": { + "ClientPluginCustomization": { "type": "object", - "description": "Tool is actively executing.", + "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Running" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "content": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" + }, + "nonce": { + "type": "string", + "description": "Opaque version token used by the host to detect changes." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallPendingResultConfirmationState": { + "DirectoryCustomization": { "type": "object", - "description": "Tool finished executing, waiting for client to approve the result.", + "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "content": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" - }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." - }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } + "$ref": "#/$defs/ChildCustomization" }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" + "type": { + "$ref": "#/$defs/CustomizationType.Directory" }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "contents": { + "$ref": "#/$defs/ChildCustomizationType", + "description": "Which child customization type this directory holds." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "writable": { + "type": "boolean", + "description": "Whether clients may write into this directory." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "enabled", + "type", + "contents", + "writable" ] }, - "ToolCallCompletedState": { + "AgentCustomization": { "type": "object", - "description": "Tool completed successfully or with an error.", + "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Agent" + }, + "description": { + "type": "string", + "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" - }, - "toolInput": { + "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." + } + }, + "required": [ + "id", + "uri", + "name", + "type" + ] + }, + "SkillCustomization": { + "type": "object", + "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", + "properties": { + "id": { "type": "string", - "description": "Raw tool input" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "name": { + "type": "string", + "description": "Human-readable name." }, - "content": { + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } - }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Completed" + "type": { + "$ref": "#/$defs/CustomizationType.Skill" }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": { + "type": "string", + "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "disableModelInvocation": { + "type": "boolean", + "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "type" ] }, - "ToolCallCancelledState": { + "PromptCustomization": { "type": "object", - "description": "Tool call was cancelled before execution.", + "description": "A prompt contributed by a plugin or directory.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "type": { + "$ref": "#/$defs/CustomizationType.Prompt" }, - "toolInput": { + "description": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Cancelled" - }, - "reason": { - "$ref": "#/$defs/ToolCallCancellationReason", - "description": "Why the tool was cancelled" - }, - "reasonMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Optional message explaining the cancellation" - }, - "userSuggestion": { - "$ref": "#/$defs/Message", - "description": "What the user suggested doing instead" - }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "description": "Short description of what the prompt does." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "reason" + "id", + "uri", + "name", + "type" ] }, - "ToolResultTextContent": { + "RuleCustomization": { "type": "object", - "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", + "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", "properties": { + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + }, + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, "type": { - "$ref": "#/$defs/ToolResultContentType.Text" + "$ref": "#/$defs/CustomizationType.Rule" }, - "text": { + "description": { "type": "string", - "description": "The text content" + "description": "Description of what the rule enforces." + }, + "alwaysApply": { + "type": "boolean", + "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." + }, + "globs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." } }, "required": [ - "type", - "text" + "id", + "uri", + "name", + "type" ] }, - "ToolResultEmbeddedResourceContent": { + "HookCustomization": { "type": "object", - "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", + "description": "A hook manifest contributed by a plugin or directory.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" - }, - "data": { + "id": { "type": "string", - "description": "Base64-encoded data" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "contentType": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Hook" } }, "required": [ - "type", - "data", - "contentType" + "id", + "uri", + "name", + "type" ] }, - "ToolResultResourceContent": { + "McpServerCustomization": { "type": "object", - "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", + "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", "properties": { + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + }, "uri": { "$ref": "#/$defs/URI", - "description": "Content URI" - }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "contentType": { + "name": { "type": "string", - "description": "Content MIME type" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, "type": { - "$ref": "#/$defs/ToolResultContentType.Resource" + "$ref": "#/$defs/CustomizationType.McpServer" + }, + "enabled": { + "type": "boolean", + "description": "Whether this MCP server is currently enabled." + }, + "state": { + "$ref": "#/$defs/McpServerState", + "description": "Current lifecycle state of the MCP server." + }, + "channel": { + "$ref": "#/$defs/URI", + "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + }, + "mcpApp": { + "$ref": "#/$defs/McpServerCustomizationApps", + "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." } }, "required": [ + "id", "uri", - "type" + "name", + "type", + "enabled", + "state" ] }, - "ToolResultFileEditContent": { + "McpServerCustomizationApps": { "type": "object", - "description": "Describes a file modification performed by a tool.", + "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", "properties": { - "before": { + "capabilities": { + "$ref": "#/$defs/AhpMcpUiHostCapabilities", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." + } + }, + "required": [ + "capabilities" + ] + }, + "AhpMcpUiHostCapabilities": { + "type": "object", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", + "properties": { + "serverTools": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state before the edit. Absent for file creations or for in-place file edits." + "description": "Producer proxies the MCP `tools/*` methods to the upstream server." }, - "after": { + "serverResources": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state after the edit. Absent for file deletions." + "description": "Producer proxies the MCP `resources/*` methods to the upstream server." }, - "diff": { + "logging": { + "type": "object", + "additionalProperties": {}, + "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + }, + "sampling": { "type": "object", "properties": { - "added": { - "type": "number" - }, - "removed": { - "type": "number" + "tools": { + "type": "string" } }, - "description": "Optional diff display metadata" - }, - "type": { - "$ref": "#/$defs/ToolResultContentType.FileEdit" + "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." + } + } + }, + "McpServerStartingState": { + "type": "object", + "description": "Server is registered with the host but has not yet started.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Starting" } }, "required": [ - "type" + "kind" ] }, - "ToolResultTerminalContent": { + "McpServerReadyState": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "Server is running and serving requests.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Terminal" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Terminal URI (subscribable for full terminal state)" - }, - "title": { - "type": "string", - "description": "Display title for the terminal content" + "kind": { + "$ref": "#/$defs/McpServerStatus.Ready" } }, "required": [ - "type", - "resource", - "title" + "kind" ] }, - "ToolResultSubagentContent": { + "McpServerAuthRequiredState": { "type": "object", - "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", + "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Subagent" + "kind": { + "$ref": "#/$defs/McpServerStatus.AuthRequired" }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Subagent session URI (subscribable for full session state)" + "reason": { + "$ref": "#/$defs/McpAuthRequiredReason", + "description": "Why authentication is required." }, - "title": { - "type": "string", - "description": "Display title for the subagent" + "resource": { + "$ref": "#/$defs/ProtectedResourceMetadata", + "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." }, - "agentName": { - "type": "string", - "description": "Internal agent name" + "requiredScopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." }, "description": { "type": "string", - "description": "Human-readable description of the subagent's task" + "description": "Human-readable hint, typically from the OAuth `error_description`." } }, "required": [ - "type", - "resource", - "title" + "kind", + "reason", + "resource" + ] + }, + "McpServerErrorState": { + "type": "object", + "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Error" + }, + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details." + } + }, + "required": [ + "kind", + "error" + ] + }, + "McpServerStoppedState": { + "type": "object", + "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Stopped" + } + }, + "required": [ + "kind" ] }, "TerminalInfo": { diff --git a/schema/state.schema.json b/schema/state.schema.json index 27057acb..c18dd94a 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -395,7 +395,7 @@ "properties": { "resource": { "$ref": "#/$defs/URI", - "description": "The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`)" + "description": "The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`)" }, "state": { "oneOf": [ @@ -593,6 +593,24 @@ "values" ] }, + "PendingMessage": { + "type": "object", + "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this pending message" + }, + "message": { + "$ref": "#/$defs/Message", + "description": "The message that will start the next turn" + } + }, + "required": [ + "id", + "message" + ] + }, "SessionState": { "type": "object", "description": "Full state for a single session, loaded when a client subscribes to the session's URI.", @@ -620,16 +638,34 @@ "$ref": "#/$defs/SessionActiveClient", "description": "The client currently providing tools and interactive capabilities to this session" }, - "chats": { + "turns": { "type": "array", "items": { - "$ref": "#/$defs/ChatSummary" + "$ref": "#/$defs/Turn" }, - "description": "Catalog of chats in this session." + "description": "Completed turns" }, - "defaultChat": { - "$ref": "#/$defs/URI", - "description": "The chat that receives input when the user addresses the session without\nselecting a specific chat. This is a UI routing hint, not a hierarchy\nmarker — chats remain equal peers at the protocol level. Hosts MAY change\nthis over the session's lifetime." + "activeTurn": { + "$ref": "#/$defs/ActiveTurn", + "description": "Currently in-progress turn" + }, + "steeringMessage": { + "$ref": "#/$defs/PendingMessage", + "description": "Message to inject into the current turn at a convenient point" + }, + "queuedMessages": { + "type": "array", + "items": { + "$ref": "#/$defs/PendingMessage" + }, + "description": "Messages to send automatically as new turns after the current turn finishes" + }, + "inputRequests": { + "type": "array", + "items": { + "$ref": "#/$defs/SessionInputRequest" + }, + "description": "Requests for user input that are currently blocking or informing session progress" }, "config": { "$ref": "#/$defs/SessionConfigState", @@ -658,7 +694,7 @@ "required": [ "summary", "lifecycle", - "chats" + "turns" ] }, "SessionActiveClient": { @@ -713,7 +749,6 @@ }, "SessionSummary": { "type": "object", - "description": "Lightweight catalog entry summarizing one session. Surfaced via\n{@link RootChannelCommands.listSessions | `root/listSessions`} and\n`root/sessionAdded`/`root/sessionSummaryChanged` notifications.\n\n**Aggregation across chats.** Once a session contains more than one chat,\nseveral `SessionSummary` fields are derived from the underlying\n{@link SessionState.chats | chat catalog}. Producers SHOULD follow these\nrules so clients that only consume the session summary (e.g. a session\nlist) still see meaningful state:\n\n- `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` /\n `Error` — bits 0–4) from the\n {@link SessionState.defaultChat | default chat} when present, else from\n the most recently modified chat. **Promote** `InputNeeded` whenever any\n chat in the session needs input, and **promote** `Error` whenever any\n chat is in an error state — both override the default-chat bits. The\n orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped.\n- `activity`: mirror the activity string of the default chat, or of the\n chat currently driving the promoted status bits when a non-default chat\n wins (e.g. the chat that raised `InputNeeded`).\n- `modifiedAt`: the max of all chats' `modifiedAt`.\n- `model` / `agent`: the session-level selection. Per-chat overrides are\n surfaced on individual {@link ChatSummary} entries, not aggregated up.\n- `workingDirectory`: the session-level **default**. Individual chats MAY\n override via {@link ChatSummary.workingDirectory}; aggregating these up\n is meaningless and SHOULD NOT be attempted.\n- `changes`: optional roll-up across all chats. Producers MAY sum the\n per-chat changeset stats or report the most expensive chat's stats —\n whichever is cheaper for the host to compute.\n\nSessions with a single chat trivially satisfy all of the above (the chat's\nvalues pass through unchanged). The rules only matter once a session\ncarries multiple chats.", "properties": { "resource": { "$ref": "#/$defs/URI", @@ -757,7 +792,7 @@ }, "workingDirectory": { "$ref": "#/$defs/URI", - "description": "The default working directory URI for this session. Individual chats\nMAY override via {@link ChatSummary.workingDirectory | their own\n`workingDirectory`}; this field acts as the fallback for any chat that\ndoes not." + "description": "The working directory URI for this session" }, "changes": { "$ref": "#/$defs/ChangesSummary", @@ -941,1774 +976,1570 @@ "values" ] }, - "ToolDefinition": { + "SessionInputOption": { "type": "object", - "description": "Describes a tool available in a session, provided by either the server or the active client.", + "description": "A choice in a select-style question.", "properties": { - "name": { + "id": { "type": "string", - "description": "Unique tool identifier" + "description": "Stable option identifier; for MCP enum values this is the enum string" }, - "title": { + "label": { "type": "string", - "description": "Human-readable display name" + "description": "Display label" }, "description": { "type": "string", - "description": "Description of what the tool does" - }, - "inputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." - }, - "outputSchema": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "properties": { - "type": "string" - }, - "required": { - "type": "string" - } - }, - "required": [ - "type" - ], - "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." - }, - "annotations": { - "$ref": "#/$defs/ToolAnnotations", - "description": "Behavioral hints about the tool. All properties are advisory." + "description": "Optional secondary text" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." + "recommended": { + "type": "boolean", + "description": "Whether this option is the recommended/default choice" } }, "required": [ - "name" + "id", + "label" ] }, - "ToolAnnotations": { + "SessionInputQuestionBase": { "type": "object", - "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "title": { + "id": { "type": "string", - "description": "Alternate human-readable title" - }, - "readOnlyHint": { - "type": "boolean", - "description": "Tool does not modify its environment (default: false)" + "description": "Stable question identifier used as the key in `answers`" }, - "destructiveHint": { - "type": "boolean", - "description": "Tool may perform destructive updates (default: true)" + "title": { + "type": "string", + "description": "Short display title" }, - "idempotentHint": { - "type": "boolean", - "description": "Repeated calls with the same arguments have no additional effect (default: false)" + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "openWorldHint": { + "required": { "type": "boolean", - "description": "Tool may interact with external entities (default: true)" + "description": "Whether the user must answer this question to accept the request" } - } + }, + "required": [ + "id", + "message" + ] }, - "CustomizationBase": { + "SessionInputTextQuestion": { "type": "object", - "description": "Fields shared by every customization variant.", + "description": "Text question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "description": "Stable question identifier used as the key in `answers`" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "title": { + "type": "string", + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Prompt shown to the user" }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.Text" + }, + "format": { + "type": "string", + "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" + }, + "min": { + "type": "number", + "description": "Minimum string length" + }, + "max": { + "type": "number", + "description": "Maximum string length" + }, + "defaultValue": { + "type": "string", + "description": "Default text" } }, "required": [ "id", - "uri", - "name" + "message", + "kind" ] }, - "CustomizationLoadingState": { + "SessionInputNumberQuestion": { "type": "object", - "description": "Container is being loaded by the host.", + "description": "Numeric question within a session input request.", "properties": { + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" + }, + "message": { + "type": "string", + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loading" + "oneOf": [ + { + "$ref": "#/$defs/SessionInputQuestionKind.Number" + }, + { + "$ref": "#/$defs/SessionInputQuestionKind.Integer" + } + ] + }, + "min": { + "type": "number", + "description": "Minimum value" + }, + "max": { + "type": "number", + "description": "Maximum value" + }, + "defaultValue": { + "type": "number", + "description": "Default numeric value" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationLoadedState": { + "SessionInputBooleanQuestion": { "type": "object", - "description": "Container loaded successfully.", + "description": "Boolean question within a session input request.", "properties": { + "id": { + "type": "string", + "description": "Stable question identifier used as the key in `answers`" + }, + "title": { + "type": "string", + "description": "Short display title" + }, + "message": { + "type": "string", + "description": "Prompt shown to the user" + }, + "required": { + "type": "boolean", + "description": "Whether the user must answer this question to accept the request" + }, "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Loaded" + "$ref": "#/$defs/SessionInputQuestionKind.Boolean" + }, + "defaultValue": { + "type": "boolean", + "description": "Default boolean value" } }, "required": [ + "id", + "message", "kind" ] }, - "CustomizationDegradedState": { + "SessionInputSingleSelectQuestion": { "type": "object", - "description": "Container partially loaded but has warnings.", + "description": "Single-select question within a session input request.", "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Degraded" - }, - "message": { + "id": { "type": "string", - "description": "Human-readable description of the warning." - } - }, - "required": [ - "kind", - "message" - ] - }, - "CustomizationErrorState": { - "type": "object", - "description": "Container failed to load.", - "properties": { - "kind": { - "$ref": "#/$defs/CustomizationLoadStatus.Error" + "description": "Stable question identifier used as the key in `answers`" }, - "message": { - "type": "string", - "description": "Human-readable error message." - } - }, - "required": [ - "kind", - "message" - ] - }, - "ContainerCustomizationBase": { - "type": "object", - "description": "Fields shared by container customizations.", - "properties": { - "id": { + "title": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Short display title" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.SingleSelect" }, - "children": { + "options": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Options the user may select from" + }, + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text instead of selecting an option" } }, "required": [ "id", - "uri", - "name", - "enabled" + "message", + "kind", + "options" ] }, - "PluginCustomization": { + "SessionInputMultiSelectQuestion": { "type": "object", - "description": "An [Open Plugins](https://open-plugins.com/) plugin.", + "description": "Multi-select question within a session input request.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable question identifier used as the key in `answers`" }, - "name": { + "title": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "Short display title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "message": { + "type": "string", + "description": "Prompt shown to the user" }, - "enabled": { + "required": { "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." + "description": "Whether the user must answer this question to accept the request" }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "kind": { + "$ref": "#/$defs/SessionInputQuestionKind.MultiSelect" }, - "children": { + "options": { "type": "array", "items": { - "$ref": "#/$defs/ChildCustomization" + "$ref": "#/$defs/SessionInputOption" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Options the user may select from" }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "allowFreeformInput": { + "type": "boolean", + "description": "Whether the user may enter text in addition to selecting options" + }, + "min": { + "type": "number", + "description": "Minimum selected item count" + }, + "max": { + "type": "number", + "description": "Maximum selected item count" } }, "required": [ "id", - "uri", - "name", - "enabled", - "type" + "message", + "kind", + "options" ] }, - "ClientPluginCustomization": { + "SessionInputRequest": { "type": "object", - "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", + "description": "A live request for user input.\n\nThe server creates or replaces requests with `session/inputRequested`.\nClients sync drafts with `session/inputAnswerChanged` and complete requests\nwith `session/inputCompleted`.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Stable request identifier" }, - "name": { + "message": { "type": "string", - "description": "Human-readable name." + "description": "Display message for the request as a whole" }, - "icons": { + "url": { + "$ref": "#/$defs/URI", + "description": "URL the user should review or open, for URL-style elicitations" + }, + "questions": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/SessionInputQuestion" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." - }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." + "description": "Ordered questions to ask the user" }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" + "answers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/SessionInputAnswer" }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + "description": "Current draft or submitted answers, keyed by question ID" + } + }, + "required": [ + "id" + ] + }, + "SessionInputTextAnswerValue": { + "type": "object", + "description": "Value captured for one answer.", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Text" }, - "type": { - "$ref": "#/$defs/CustomizationType.Plugin" + "value": { + "type": "string" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputNumberAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Number" }, - "nonce": { - "type": "string", - "description": "Opaque version token used by the host to detect changes." + "value": { + "type": "number" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type" + "kind", + "value" ] }, - "DirectoryCustomization": { + "SessionInputBooleanAnswerValue": { "type": "object", - "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Boolean" }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "value": { + "type": "boolean" + } + }, + "required": [ + "kind", + "value" + ] + }, + "SessionInputSelectedAnswerValue": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.Selected" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "string" }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "enabled": { - "type": "boolean", - "description": "Whether this container is currently enabled." - }, - "clientId": { - "type": "string", - "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." - }, - "load": { - "$ref": "#/$defs/CustomizationLoadState", - "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." - }, - "children": { - "type": "array", - "items": { - "$ref": "#/$defs/ChildCustomization" - }, - "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Directory" - }, - "contents": { - "$ref": "#/$defs/ChildCustomizationType", - "description": "Which child customization type this directory holds." - }, - "writable": { - "type": "boolean", - "description": "Whether clients may write into this directory." + "description": "Free-form text entered instead of selecting an option" } }, "required": [ - "id", - "uri", - "name", - "enabled", - "type", - "contents", - "writable" + "kind", + "value" ] }, - "AgentCustomization": { + "SessionInputSelectedManyAnswerValue": { "type": "object", - "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "kind": { + "$ref": "#/$defs/SessionInputAnswerValueKind.SelectedMany" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "type": "array", + "items": { + "type": "string" + } }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Agent" - }, - "description": { - "type": "string", - "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." + "description": "Free-form text entered in addition to selected options" } }, "required": [ - "id", - "uri", - "name", - "type" + "kind", + "value" ] }, - "SkillCustomization": { + "SessionInputAnswered": { "type": "object", - "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "state": { + "oneOf": [ + { + "$ref": "#/$defs/SessionInputAnswerState.Draft" + }, + { + "$ref": "#/$defs/SessionInputAnswerState.Submitted" + } + ], + "description": "Answer state" }, - "name": { - "type": "string", - "description": "Human-readable name." + "value": { + "$ref": "#/$defs/SessionInputAnswerValue", + "description": "Answer value" + } + }, + "required": [ + "state", + "value" + ] + }, + "SessionInputSkipped": { + "type": "object", + "properties": { + "state": { + "$ref": "#/$defs/SessionInputAnswerState.Skipped", + "description": "Answer state" }, - "icons": { + "freeformValues": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "type": "string" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Skill" - }, - "description": { - "type": "string", - "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." - }, - "disableModelInvocation": { - "type": "boolean", - "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." + "description": "Free-form reason or value captured while skipping, if any" } }, "required": [ - "id", - "uri", - "name", - "type" + "state" ] }, - "PromptCustomization": { + "Turn": { "type": "object", - "description": "A prompt contributed by a plugin or directory.", + "description": "A completed request/response cycle.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" }, - "type": { - "$ref": "#/$defs/CustomizationType.Prompt" + "state": { + "$ref": "#/$defs/TurnState", + "description": "How the turn ended" }, - "description": { - "type": "string", - "description": "Short description of what the prompt does." + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details if state is `'error'`" } }, "required": [ "id", - "uri", - "name", - "type" + "message", + "responseParts", + "usage", + "state" ] }, - "RuleCustomization": { + "ActiveTurn": { "type": "object", - "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", + "description": "An in-progress turn — the assistant is actively streaming.", "properties": { "id": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Turn identifier" }, - "name": { - "type": "string", - "description": "Human-readable name." + "message": { + "$ref": "#/$defs/Message", + "description": "The message that initiated the turn" }, - "icons": { + "responseParts": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/ResponsePart" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.Rule" - }, - "description": { - "type": "string", - "description": "Description of what the rule enforces." - }, - "alwaysApply": { - "type": "boolean", - "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." + "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." }, - "globs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." + "usage": { + "$ref": "#/$defs/UsageInfo", + "description": "Token usage info" } }, "required": [ "id", - "uri", - "name", - "type" + "message", + "responseParts", + "usage" ] }, - "HookCustomization": { + "Message": { "type": "object", - "description": "A hook manifest contributed by a plugin or directory.", + "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", "properties": { - "id": { + "text": { "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + "description": "Message text" }, - "name": { - "type": "string", - "description": "Human-readable name." + "origin": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + }, + "required": [ + "kind" + ], + "description": "The origin of the message" }, - "icons": { + "attachments": { "type": "array", "items": { - "$ref": "#/$defs/Icon" + "$ref": "#/$defs/MessageAttachment" }, - "description": "Icons for UI display." - }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + "description": "File/selection attachments" }, - "type": { - "$ref": "#/$defs/CustomizationType.Hook" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." } }, "required": [ - "id", - "uri", - "name", - "type" + "text", + "origin" ] }, - "McpServerCustomization": { + "MessageAttachmentBase": { "type": "object", - "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", + "description": "Common fields shared by all {@link MessageAttachment} variants.", "properties": { - "id": { - "type": "string", - "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." - }, - "name": { + "label": { "type": "string", - "description": "Human-readable name." - }, - "icons": { - "type": "array", - "items": { - "$ref": "#/$defs/Icon" - }, - "description": "Icons for UI display." + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, "range": { "$ref": "#/$defs/TextRange", - "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." - }, - "type": { - "$ref": "#/$defs/CustomizationType.McpServer" - }, - "enabled": { - "type": "boolean", - "description": "Whether this MCP server is currently enabled." - }, - "state": { - "$ref": "#/$defs/McpServerState", - "description": "Current lifecycle state of the MCP server." + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "channel": { - "$ref": "#/$defs/URI", - "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." }, - "mcpApp": { - "$ref": "#/$defs/McpServerCustomizationApps", - "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." } }, "required": [ - "id", - "uri", - "name", - "type", - "enabled", - "state" + "label" ] }, - "McpServerCustomizationApps": { + "SimpleMessageAttachment": { "type": "object", - "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", + "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", "properties": { - "capabilities": { - "$ref": "#/$defs/AhpMcpUiHostCapabilities", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Simple", + "description": "Discriminant" + }, + "modelRepresentation": { + "type": "string", + "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." } }, "required": [ - "capabilities" + "label", + "type" ] }, - "AhpMcpUiHostCapabilities": { + "MessageEmbeddedResourceAttachment": { "type": "object", - "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", + "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", "properties": { - "serverTools": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `tools/*` methods to the upstream server." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." }, - "serverResources": { - "type": "object", - "properties": { - "listChanged": { - "type": "boolean" - } - }, - "description": "Producer proxies the MCP `resources/*` methods to the upstream server." + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." }, - "logging": { + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { "type": "object", "additionalProperties": {}, - "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." }, - "sampling": { - "type": "object", - "properties": { - "tools": { - "type": "string" - } - }, - "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." - } - } - }, - "McpServerStartingState": { - "type": "object", - "description": "Server is registered with the host but has not yet started.", - "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.Starting" + "type": { + "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", + "description": "Discriminant" + }, + "data": { + "type": "string", + "description": "Base64-encoded binary data" + }, + "contentType": { + "type": "string", + "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + }, + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "kind" + "label", + "type", + "data", + "contentType" ] }, - "McpServerReadyState": { + "MessageResourceAttachment": { "type": "object", - "description": "Server is running and serving requests.", + "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.Ready" + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Resource", + "description": "Discriminant" + }, + "selection": { + "$ref": "#/$defs/TextSelection", + "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." } }, "required": [ - "kind" + "label", + "uri", + "type" ] }, - "McpServerAuthRequiredState": { + "MessageAnnotationsAttachment": { "type": "object", - "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", + "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", "properties": { - "kind": { - "$ref": "#/$defs/McpServerStatus.AuthRequired" - }, - "reason": { - "$ref": "#/$defs/McpAuthRequiredReason", - "description": "Why authentication is required." + "label": { + "type": "string", + "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + }, + "displayKind": { + "type": "string", + "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + }, + "type": { + "$ref": "#/$defs/MessageAttachmentKind.Annotations", + "description": "Discriminant" }, "resource": { - "$ref": "#/$defs/ProtectedResourceMetadata", - "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." + "$ref": "#/$defs/URI", + "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." }, - "requiredScopes": { + "annotationIds": { "type": "array", "items": { "type": "string" }, - "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." - }, - "description": { - "type": "string", - "description": "Human-readable hint, typically from the OAuth `error_description`." + "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." } }, "required": [ - "kind", - "reason", + "label", + "type", "resource" ] }, - "McpServerErrorState": { + "MarkdownResponsePart": { "type": "object", - "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", "properties": { "kind": { - "$ref": "#/$defs/McpServerStatus.Error" + "$ref": "#/$defs/ResponsePartKind.Markdown", + "description": "Discriminant" }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details." + "id": { + "type": "string", + "description": "Part identifier, used by `session/delta` to target this part for content appends" + }, + "content": { + "type": "string", + "description": "Markdown content" } }, "required": [ "kind", - "error" + "id", + "content" ] }, - "McpServerStoppedState": { + "ResourceReponsePart": { "type": "object", - "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", + "description": "A content part that's a reference to large content stored outside the state tree.", "properties": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Content URI" + }, + "sizeHint": { + "type": "number", + "description": "Approximate size in bytes" + }, + "contentType": { + "type": "string", + "description": "Content MIME type" + }, "kind": { - "$ref": "#/$defs/McpServerStatus.Stopped" + "$ref": "#/$defs/ResponsePartKind.ContentRef", + "description": "Discriminant" } }, "required": [ + "uri", "kind" ] }, - "ChatState": { + "ToolCallResponsePart": { "type": "object", - "description": "Full state for a single chat, loaded when a client subscribes to the chat's\nURI.\n\nThe lightweight catalog representation of a chat is {@link ChatSummary},\ncarried in {@link SessionState.chats | `SessionState.chats`}. `ChatState`\n**denormalizes** every {@link ChatSummary} field directly onto itself so\nsubscribers receive one flat object instead of having to merge a nested\n`summary` sub-object. Producers MUST keep the two representations\nconsistent: any change to the inlined fields below SHOULD also be\nannounced on the parent session via the matching\n{@link SessionChatUpdatedAction | `session/chatUpdated`} action.", + "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" - }, - "activity": { - "type": "string", - "description": "Human-readable description of what the chat is currently doing" - }, - "modifiedAt": { - "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nHosts MAY override this for individual chats — for example, to give a\nsubordinate chat its own git worktree so multiple chats in a session can\nmake independent edits that the orchestrator later merges back." - }, - "turns": { - "type": "array", - "items": { - "$ref": "#/$defs/Turn" - }, - "description": "Completed turns" - }, - "activeTurn": { - "$ref": "#/$defs/ActiveTurn", - "description": "Currently in-progress turn" - }, - "steeringMessage": { - "$ref": "#/$defs/PendingMessage", - "description": "Message to inject into the current turn at a convenient point" - }, - "queuedMessages": { - "type": "array", - "items": { - "$ref": "#/$defs/PendingMessage" - }, - "description": "Messages to send automatically as new turns after the current turn finishes" - }, - "inputRequests": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputRequest" - }, - "description": "Requests for user input that are currently blocking or informing chat progress" + "kind": { + "$ref": "#/$defs/ResponsePartKind.ToolCall", + "description": "Discriminant" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this chat." + "toolCall": { + "$ref": "#/$defs/ToolCallState", + "description": "Full tool call lifecycle state" } }, "required": [ - "resource", - "title", - "status", - "modifiedAt", - "turns" + "kind", + "toolCall" ] }, - "ChatSummary": { + "ReasoningResponsePart": { "type": "object", - "description": "Lightweight catalog entry for a chat, carried in\n{@link SessionState.chats | `SessionState.chats`}. The full conversation\nlives in {@link ChatState}, which inlines (denormalizes) every field below.", + "description": "Reasoning/thinking content from the model.", "properties": { - "resource": { - "$ref": "#/$defs/URI", - "description": "Chat URI" - }, - "title": { - "type": "string", - "description": "Chat title" - }, - "status": { - "$ref": "#/$defs/SessionStatus", - "description": "Current chat status (reuses SessionStatus shape)" + "kind": { + "$ref": "#/$defs/ResponsePartKind.Reasoning", + "description": "Discriminant" }, - "activity": { + "id": { "type": "string", - "description": "Human-readable description of what the chat is currently doing" + "description": "Part identifier, used by `session/reasoning` to target this part for content appends" }, - "modifiedAt": { + "content": { "type": "string", - "description": "Last modification timestamp (ISO 8601, e.g. `\"2025-03-10T18:42:03.123Z\"`)" - }, - "model": { - "$ref": "#/$defs/ModelSelection", - "description": "Optional per-chat model override (defaults to the session's model)" - }, - "agent": { - "$ref": "#/$defs/AgentSelection", - "description": "Optional per-chat agent override (defaults to the session's agent)" - }, - "origin": { - "$ref": "#/$defs/ChatOrigin", - "description": "How this chat came into existence" - }, - "workingDirectory": { - "$ref": "#/$defs/URI", - "description": "Optional per-chat working directory.\n\nIf absent, the chat inherits\n{@link SessionSummary.workingDirectory | the session's working directory}.\nSee {@link ChatState.workingDirectory} for usage notes." + "description": "Accumulated reasoning text" } }, "required": [ - "resource", - "title", - "status", - "modifiedAt" + "kind", + "id", + "content" ] }, - "PendingMessage": { + "SystemNotificationResponsePart": { "type": "object", - "description": "A message queued for future delivery to the agent.\n\nSteering messages are injected into the current turn mid-flight.\nQueued messages are automatically started as new turns after the\ncurrent turn naturally finishes.", + "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", "properties": { - "id": { - "type": "string", - "description": "Unique identifier for this pending message" + "kind": { + "$ref": "#/$defs/ResponsePartKind.SystemNotification", + "description": "Discriminant" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that will start the next turn" + "content": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "The text of the system notification" } }, "required": [ - "id", - "message" + "kind", + "content" ] }, - "ChatInputOption": { + "ConfirmationOption": { "type": "object", - "description": "A choice in a select-style question.", + "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", "properties": { "id": { "type": "string", - "description": "Stable option identifier; for MCP enum values this is the enum string" + "description": "Unique identifier for the option, returned in the confirmed action" }, "label": { "type": "string", - "description": "Display label" + "description": "Human-readable label displayed to the user" }, - "description": { - "type": "string", - "description": "Optional secondary text" + "kind": { + "$ref": "#/$defs/ConfirmationOptionKind", + "description": "Whether this option represents an approval or denial" }, - "recommended": { - "type": "boolean", - "description": "Whether this option is the recommended/default choice" + "group": { + "type": "number", + "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." } }, "required": [ "id", - "label" + "label", + "kind" ] }, - "ChatInputQuestionBase": { + "ToolCallClientContributor": { "type": "object", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.Client" }, - "title": { + "clientId": { "type": "string", - "description": "Short display title" + "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `session/toolCallComplete` with the result." + } + }, + "required": [ + "kind", + "clientId" + ] + }, + "ToolCallMcpContributor": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/$defs/ToolCallContributorKind.MCP" }, - "message": { + "customizationId": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." } }, "required": [ - "id", - "message" + "kind", + "customizationId" ] }, - "ChatInputTextQuestion": { + "ToolCallBase": { "type": "object", - "description": "Text question within a chat input request.", + "description": "Metadata common to all tool call states.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" - }, - "message": { - "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" - }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Text" - }, - "format": { - "type": "string", - "description": "Format hint for text questions, such as `email`, `uri`, `date`, or `date-time`" - }, - "min": { - "type": "number", - "description": "Minimum string length" - }, - "max": { - "type": "number", - "description": "Maximum string length" - }, - "defaultValue": { - "type": "string", - "description": "Default text" - } - }, - "required": [ - "id", - "message", - "kind" - ] - }, - "ChatInputNumberQuestion": { - "type": "object", - "description": "Numeric question within a chat input request.", - "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" - }, - "kind": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputQuestionKind.Number" - }, - { - "$ref": "#/$defs/ChatInputQuestionKind.Integer" - } - ] - }, - "min": { - "type": "number", - "description": "Minimum value" + "description": "Human-readable tool name" }, - "max": { - "type": "number", - "description": "Maximum value" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "defaultValue": { - "type": "number", - "description": "Default numeric value" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." } }, "required": [ - "id", - "message", - "kind" + "toolCallId", + "toolName", + "displayName" ] }, - "ChatInputBooleanQuestion": { + "ToolCallParameterFields": { "type": "object", - "description": "Boolean question within a chat input request.", + "description": "Properties available once tool call parameters are fully received.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "message": { + "toolInput": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" - }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.Boolean" - }, - "defaultValue": { - "type": "boolean", - "description": "Default boolean value" + "description": "Raw tool input" } }, "required": [ - "id", - "message", - "kind" + "invocationMessage" ] }, - "ChatInputSingleSelectQuestion": { + "ToolCallResult": { "type": "object", - "description": "Single-select question within a chat input request.", + "description": "Tool execution result details, available after execution completes.", "properties": { - "id": { - "type": "string", - "description": "Stable question identifier used as the key in `answers`" - }, - "title": { - "type": "string", - "description": "Short display title" - }, - "message": { - "type": "string", - "description": "Prompt shown to the user" - }, - "required": { + "success": { "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "description": "Whether the tool succeeded" }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.SingleSelect" + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" }, - "options": { + "content": { "type": "array", "items": { - "$ref": "#/$defs/ChatInputOption" + "$ref": "#/$defs/ToolResultContent" }, - "description": "Options the user may select from" + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text instead of selecting an option" + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + }, + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" } }, "required": [ - "id", - "message", - "kind", - "options" + "success", + "pastTenseMessage" ] }, - "ChatInputMultiSelectQuestion": { + "ToolCallStreamingState": { "type": "object", - "description": "Multi-select question within a chat input request.", + "description": "LM is streaming the tool call parameters.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable question identifier used as the key in `answers`" + "description": "Unique tool call identifier" }, - "title": { + "toolName": { "type": "string", - "description": "Short display title" + "description": "Internal tool name (for debugging/logging)" }, - "message": { + "displayName": { "type": "string", - "description": "Prompt shown to the user" - }, - "required": { - "type": "boolean", - "description": "Whether the user must answer this question to accept the request" + "description": "Human-readable tool name" }, - "kind": { - "$ref": "#/$defs/ChatInputQuestionKind.MultiSelect" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "options": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputOption" - }, - "description": "Options the user may select from" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "allowFreeformInput": { - "type": "boolean", - "description": "Whether the user may enter text in addition to selecting options" + "status": { + "$ref": "#/$defs/ToolCallStatus.Streaming" }, - "min": { - "type": "number", - "description": "Minimum selected item count" + "partialInput": { + "type": "string", + "description": "Partial parameters accumulated so far" }, - "max": { - "type": "number", - "description": "Maximum selected item count" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Progress message shown while parameters are streaming" } }, "required": [ - "id", - "message", - "kind", - "options" + "toolCallId", + "toolName", + "displayName", + "status" ] }, - "ChatInputRequest": { + "ToolCallPendingConfirmationState": { "type": "object", - "description": "A live request for user input.\n\nThe server creates or replaces requests with `chat/inputRequested`.\nClients sync drafts with `chat/inputAnswerChanged` and complete requests\nwith `chat/inputCompleted`.", + "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Stable request identifier" + "description": "Unique tool call identifier" }, - "message": { + "toolName": { "type": "string", - "description": "Display message for the request as a whole" + "description": "Internal tool name (for debugging/logging)" }, - "url": { - "$ref": "#/$defs/URI", - "description": "URL the user should review or open, for URL-style elicitations" + "displayName": { + "type": "string", + "description": "Human-readable tool name" }, - "questions": { - "type": "array", - "items": { - "$ref": "#/$defs/ChatInputQuestion" - }, - "description": "Ordered questions to ask the user" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "answers": { + "_meta": { "type": "object", - "additionalProperties": { - "$ref": "#/$defs/ChatInputAnswer" - }, - "description": "Current draft or submitted answers, keyed by question ID" - } - }, - "required": [ - "id" - ] - }, - "ChatInputTextAnswerValue": { - "type": "object", - "description": "Value captured for one answer.", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Text" - }, - "value": { - "type": "string" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputNumberAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Number" - }, - "value": { - "type": "number" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputBooleanAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Boolean" + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "value": { - "type": "boolean" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.Selected" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "value": { - "type": "string" + "toolInput": { + "type": "string", + "description": "Raw tool input" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form text entered instead of selecting an option" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputSelectedManyAnswerValue": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/$defs/ChatInputAnswerValueKind.SelectedMany" + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" }, - "value": { - "type": "array", - "items": { - "type": "string" - } + "confirmationTitle": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" }, - "freeformValues": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Free-form text entered in addition to selected options" - } - }, - "required": [ - "kind", - "value" - ] - }, - "ChatInputAnswered": { - "type": "object", - "properties": { - "state": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputAnswerState.Draft" - }, - { - "$ref": "#/$defs/ChatInputAnswerState.Submitted" + "edits": { + "type": "object", + "properties": { + "items": { + "type": "string" } + }, + "required": [ + "items" ], - "description": "Answer state" + "description": "File edits that this tool call will perform, for preview before confirmation" }, - "value": { - "$ref": "#/$defs/ChatInputAnswerValue", - "description": "Answer value" - } - }, - "required": [ - "state", - "value" - ] - }, - "ChatInputSkipped": { - "type": "object", - "properties": { - "state": { - "$ref": "#/$defs/ChatInputAnswerState.Skipped", - "description": "Answer state" + "editable": { + "type": "boolean", + "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" }, - "freeformValues": { + "options": { "type": "array", "items": { - "type": "string" + "$ref": "#/$defs/ConfirmationOption" }, - "description": "Free-form reason or value captured while skipping, if any" + "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." } }, "required": [ - "state" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status" ] }, - "Turn": { + "ToolCallRunningState": { "type": "object", - "description": "A completed request/response cycle.", + "description": "Tool is actively executing.", "properties": { - "id": { + "toolCallId": { "type": "string", - "description": "Turn identifier" + "description": "Unique tool call identifier" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "responseParts": { - "type": "array", - "items": { - "$ref": "#/$defs/ResponsePart" - }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nConsumers should derive display text by concatenating markdown parts,\nand find tool calls by filtering for `ToolCall` parts." + "displayName": { + "type": "string", + "description": "Human-readable tool name" }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, - "state": { - "$ref": "#/$defs/TurnState", - "description": "How the turn ended" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "error": { - "$ref": "#/$defs/ErrorInfo", - "description": "Error details if state is `'error'`" - } - }, - "required": [ - "id", - "message", - "responseParts", - "usage", - "state" - ] - }, - "ActiveTurn": { - "type": "object", - "description": "An in-progress turn — the assistant is actively streaming.", - "properties": { - "id": { + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { "type": "string", - "description": "Turn identifier" + "description": "Raw tool input" }, - "message": { - "$ref": "#/$defs/Message", - "description": "The message that initiated the turn" + "status": { + "$ref": "#/$defs/ToolCallStatus.Running" }, - "responseParts": { + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" + }, + "content": { "type": "array", "items": { - "$ref": "#/$defs/ResponsePart" + "$ref": "#/$defs/ToolResultContent" }, - "description": "All response content in stream order: text, tool calls, reasoning, and content refs.\n\nTool call parts include `pendingPermissions` when permissions are awaiting user approval." - }, - "usage": { - "$ref": "#/$defs/UsageInfo", - "description": "Token usage info" + "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." } }, "required": [ - "id", - "message", - "responseParts", - "usage" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "confirmed" ] }, - "Message": { + "ToolCallPendingResultConfirmationState": { "type": "object", - "description": "A message that initiates or steers a turn. Messages can originate from the\nuser or be system-generated (see {@link MessageKind}).\n\nAttachments MAY be referenced inside {@link Message.text} via their\n{@link MessageAttachmentBase.range} field. Attachments without a range are\nstill associated with the message but do not correspond to a specific span\nin the text.", + "description": "Tool finished executing, waiting for client to approve the result.", "properties": { - "text": { + "toolCallId": { "type": "string", - "description": "Message text" + "description": "Unique tool call identifier" }, - "origin": { + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" + }, + "displayName": { + "type": "string", + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." + }, + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + }, + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" + }, + "toolInput": { + "type": "string", + "description": "Raw tool input" + }, + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" + }, + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + }, + "structuredContent": { + "type": "object", + "additionalProperties": {}, + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + }, + "error": { "type": "object", "properties": { - "kind": { + "message": { + "type": "string" + }, + "code": { "type": "string" } }, "required": [ - "kind" + "message" ], - "description": "The origin of the message" + "description": "Error details if the tool failed" }, - "attachments": { - "type": "array", - "items": { - "$ref": "#/$defs/MessageAttachment" - }, - "description": "File/selection attachments" + "status": { + "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this message.\n\nClients MAY look for well-known keys here to provide enhanced UI, and\nagent hosts MAY use it to carry context that does not fit any other\nfield. Mirrors the MCP `_meta` convention." + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "text", - "origin" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "MessageAttachmentBase": { + "ToolCallCompletedState": { "type": "object", - "description": "Common fields shared by all {@link MessageAttachment} variants.", + "description": "Tool completed successfully or with an error.", "properties": { - "label": { + "toolCallId": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool call identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "displayKind": { + "displayName": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - } - }, - "required": [ - "label" - ] - }, - "SimpleMessageAttachment": { - "type": "object", - "description": "A simple, opaque attachment whose model representation is described by\nthe producer.", - "properties": { - "label": { - "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "displayKind": { + "toolInput": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Raw tool input" }, - "_meta": { + "success": { + "type": "boolean", + "description": "Whether the tool succeeded" + }, + "pastTenseMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Past-tense description of what the tool did" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/$defs/ToolResultContent" + }, + "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." + }, + "structuredContent": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Simple", - "description": "Discriminant" + "error": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "message" + ], + "description": "Error details if the tool failed" }, - "modelRepresentation": { - "type": "string", - "description": "Representation of the attachment as it should be shown to the model.\n\nIf the attachment was produced by the client, this property MUST be\ndefined so the agent host can correctly interpret the attachment. This\nproperty MAY be omitted when the attachment originated from a\n`completions` response." + "status": { + "$ref": "#/$defs/ToolCallStatus.Completed" + }, + "confirmed": { + "$ref": "#/$defs/ToolCallConfirmationReason", + "description": "How the tool was confirmed for execution" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "label", - "type" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "success", + "pastTenseMessage", + "status", + "confirmed" ] }, - "MessageEmbeddedResourceAttachment": { + "ToolCallCancelledState": { "type": "object", - "description": "An attachment whose data is embedded inline as a base64 string.\n\nUse this for small binary payloads (e.g. a pasted image) that should be\ndelivered with the user message itself rather than fetched separately.", + "description": "Tool call was cancelled before execution.", "properties": { - "label": { + "toolCallId": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool call identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "toolName": { + "type": "string", + "description": "Internal tool name (for debugging/logging)" }, - "displayKind": { + "displayName": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Human-readable tool name" + }, + "contributor": { + "$ref": "#/$defs/ToolCallContributor", + "description": "Reference to the contributor of the tool being called." }, "_meta": { "type": "object", "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.EmbeddedResource", - "description": "Discriminant" + "invocationMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Message describing what the tool will do" }, - "data": { + "toolInput": { "type": "string", - "description": "Base64-encoded binary data" + "description": "Raw tool input" }, - "contentType": { - "type": "string", - "description": "Content MIME type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + "status": { + "$ref": "#/$defs/ToolCallStatus.Cancelled" + }, + "reason": { + "$ref": "#/$defs/ToolCallCancellationReason", + "description": "Why the tool was cancelled" + }, + "reasonMessage": { + "$ref": "#/$defs/StringOrMarkdown", + "description": "Optional message explaining the cancellation" }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the attached textual resource.\n\nOnly meaningful for textual resources." + "userSuggestion": { + "$ref": "#/$defs/Message", + "description": "What the user suggested doing instead" + }, + "selectedOption": { + "$ref": "#/$defs/ConfirmationOption", + "description": "The confirmation option the user selected, if confirmation options were provided" } }, "required": [ - "label", - "type", - "data", - "contentType" + "toolCallId", + "toolName", + "displayName", + "invocationMessage", + "status", + "reason" ] }, - "MessageResourceAttachment": { + "ToolDefinition": { "type": "object", - "description": "An attachment that references a resource by URI. The content is not\ndelivered inline; consumers can fetch it via `resourceRead` when needed.", + "description": "Describes a tool available in a session, provided by either the server or the active client.", "properties": { - "label": { + "name": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Unique tool identifier" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "title": { + "type": "string", + "description": "Human-readable display name" }, - "displayKind": { + "description": { "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "description": "Description of what the tool does" }, - "_meta": { + "inputSchema": { "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." - }, - "uri": { - "$ref": "#/$defs/URI", - "description": "Content URI" - }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } + }, + "required": [ + "type" + ], + "description": "JSON Schema defining the expected input parameters.\n\nOptional because client-provided tools may not have formal schemas.\nMirrors MCP `Tool.inputSchema`." }, - "contentType": { - "type": "string", - "description": "Content MIME type" + "outputSchema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "properties": { + "type": "string" + }, + "required": { + "type": "string" + } + }, + "required": [ + "type" + ], + "description": "JSON Schema defining the structure of the tool's output.\n\nMirrors MCP `Tool.outputSchema`." }, - "type": { - "$ref": "#/$defs/MessageAttachmentKind.Resource", - "description": "Discriminant" + "annotations": { + "$ref": "#/$defs/ToolAnnotations", + "description": "Behavioral hints about the tool. All properties are advisory." }, - "selection": { - "$ref": "#/$defs/TextSelection", - "description": "Optional selection within the referenced textual resource.\n\nOnly meaningful for textual resources." + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata.\n\nMirrors the MCP `_meta` convention." } }, "required": [ - "label", - "uri", - "type" + "name" ] }, - "MessageAnnotationsAttachment": { + "ToolAnnotations": { "type": "object", - "description": "An attachment that references annotations on a session's annotations\nchannel (see {@link AnnotationsState}).\n\nWhen {@link annotationIds} is omitted the attachment references every\nannotation on the channel; when present it references only the listed\n{@link Annotation.id | annotation ids}.", + "description": "Behavioral hints about a tool. All properties are advisory and not\nguaranteed to faithfully describe tool behavior.\n\nMirrors MCP `ToolAnnotations` from the Model Context Protocol specification.", "properties": { - "label": { + "title": { "type": "string", - "description": "A human-readable label for the attachment (e.g. the filename of a file\nattachment). Used for display in UI." + "description": "Alternate human-readable title" }, - "range": { - "$ref": "#/$defs/TextRange", - "description": "If defined, the range in {@link Message.text} that references this\nattachment. This is a text range, not a byte range." + "readOnlyHint": { + "type": "boolean", + "description": "Tool does not modify its environment (default: false)" }, - "displayKind": { - "type": "string", - "description": "Advisory display hint for clients rendering this attachment. Recognized\nvalues include:\n\n- `'image'`: the attachment is an image\n- `'document'`: the attachment is a textual document\n- `'symbol'`: the attachment is a code symbol (e.g. a function or class)\n- `'directory'`: the attachment is a folder\n- `'selection'`: the attachment is a selection within a document\n\nImplementations MAY provide additional values; clients SHOULD fall back\nto a reasonable default when an unknown value is encountered." + "destructiveHint": { + "type": "boolean", + "description": "Tool may perform destructive updates (default: true)" }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional implementation-defined metadata for the attachment.\n\nIf the attachment was produced by the `completions` command, the client\nMUST preserve every property of `_meta` originally returned by the agent\nhost when sending the user message containing the accepted completion." + "idempotentHint": { + "type": "boolean", + "description": "Repeated calls with the same arguments have no additional effect (default: false)" }, + "openWorldHint": { + "type": "boolean", + "description": "Tool may interact with external entities (default: true)" + } + } + }, + "ToolResultTextContent": { + "type": "object", + "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", + "properties": { "type": { - "$ref": "#/$defs/MessageAttachmentKind.Annotations", - "description": "Discriminant" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "The annotations channel URI (typically `ahp-session://annotations`).\nMatches {@link AnnotationsSummary.resource}." + "$ref": "#/$defs/ToolResultContentType.Text" }, - "annotationIds": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Specific {@link Annotation.id | annotation ids} to reference. When\nomitted, the attachment references all annotations on the channel." + "text": { + "type": "string", + "description": "The text content" } }, "required": [ - "label", "type", - "resource" + "text" ] }, - "MarkdownResponsePart": { + "ToolResultEmbeddedResourceContent": { "type": "object", + "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Markdown", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" }, - "id": { + "data": { "type": "string", - "description": "Part identifier, used by `chat/delta` to target this part for content appends" + "description": "Base64-encoded data" }, - "content": { + "contentType": { "type": "string", - "description": "Markdown content" + "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" } }, "required": [ - "kind", - "id", - "content" + "type", + "data", + "contentType" ] }, - "ResourceReponsePart": { + "ToolResultResourceContent": { "type": "object", - "description": "A content part that's a reference to large content stored outside the state tree.", + "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", "properties": { "uri": { "$ref": "#/$defs/URI", @@ -2722,819 +2553,874 @@ "type": "string", "description": "Content MIME type" }, - "kind": { - "$ref": "#/$defs/ResponsePartKind.ContentRef", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Resource" } }, "required": [ "uri", - "kind" + "type" ] }, - "ToolCallResponsePart": { + "ToolResultFileEditContent": { "type": "object", - "description": "A tool call represented as a response part.\n\nTool calls are part of the response stream, interleaved with text and\nreasoning. The `toolCall.toolCallId` serves as the part identifier for\nactions that target this part.", + "description": "Describes a file modification performed by a tool.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.ToolCall", - "description": "Discriminant" + "before": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state before the edit. Absent for file creations or for in-place file edits." }, - "toolCall": { - "$ref": "#/$defs/ToolCallState", - "description": "Full tool call lifecycle state" + "after": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "content": { + "type": "string" + } + }, + "required": [ + "uri", + "content" + ], + "description": "The file state after the edit. Absent for file deletions." + }, + "diff": { + "type": "object", + "properties": { + "added": { + "type": "number" + }, + "removed": { + "type": "number" + } + }, + "description": "Optional diff display metadata" + }, + "type": { + "$ref": "#/$defs/ToolResultContentType.FileEdit" } }, "required": [ - "kind", - "toolCall" + "type" ] }, - "ReasoningResponsePart": { + "ToolResultTerminalContent": { "type": "object", - "description": "Reasoning/thinking content from the model.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.Reasoning", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Terminal" }, - "id": { - "type": "string", - "description": "Part identifier, used by `chat/reasoning` to target this part for content appends" + "resource": { + "$ref": "#/$defs/URI", + "description": "Terminal URI (subscribable for full terminal state)" }, - "content": { + "title": { "type": "string", - "description": "Accumulated reasoning text" + "description": "Display title for the terminal content" } }, "required": [ - "kind", - "id", - "content" + "type", + "resource", + "title" ] }, - "SystemNotificationResponsePart": { + "ToolResultSubagentContent": { "type": "object", - "description": "A system notification surfaced as part of the response stream.\n\nSystem notifications are messages authored by the agent harness\nthat need to be visible to both the agent (for situational awareness) and\nthe user (for transcript continuity). Examples include \"background subagent\nX completed\" or \"task Y was cancelled\".", + "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", "properties": { - "kind": { - "$ref": "#/$defs/ResponsePartKind.SystemNotification", - "description": "Discriminant" + "type": { + "$ref": "#/$defs/ToolResultContentType.Subagent" }, - "content": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "The text of the system notification" + "resource": { + "$ref": "#/$defs/URI", + "description": "Subagent session URI (subscribable for full session state)" + }, + "title": { + "type": "string", + "description": "Display title for the subagent" + }, + "agentName": { + "type": "string", + "description": "Internal agent name" + }, + "description": { + "type": "string", + "description": "Human-readable description of the subagent's task" } }, "required": [ - "kind", - "content" + "type", + "resource", + "title" ] }, - "ConfirmationOption": { + "CustomizationBase": { "type": "object", - "description": "A confirmation option that the server offers for a tool call awaiting\napproval. Allows richer choices beyond simple approve/deny — for example,\n\"Approve in this Session\" or \"Deny with reason.\"", + "description": "Fields shared by every customization variant.", "properties": { "id": { "type": "string", - "description": "Unique identifier for the option, returned in the confirmed action" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "label": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Human-readable label displayed to the user" + "description": "Human-readable name." }, - "kind": { - "$ref": "#/$defs/ConfirmationOptionKind", - "description": "Whether this option represents an approval or denial" + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "group": { - "type": "number", - "description": "Logical group number for visual categorisation.\n\nClients SHOULD display options in the order they are defined and MAY\nuse differing group numbers to insert dividers between logical clusters\nof options." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." } }, "required": [ "id", - "label", - "kind" + "uri", + "name" ] }, - "ToolCallClientContributor": { + "CustomizationLoadingState": { "type": "object", + "description": "Container is being loaded by the host.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.Client" - }, - "clientId": { - "type": "string", - "description": "If this tool is provided by a client, the `clientId` of the owning client.\nAbsent for server-side tools.\n\nWhen set, the identified client is responsible for executing the tool and\ndispatching `chat/toolCallComplete` with the result." + "$ref": "#/$defs/CustomizationLoadStatus.Loading" } }, "required": [ - "kind", - "clientId" + "kind" ] }, - "ToolCallMcpContributor": { + "CustomizationLoadedState": { "type": "object", + "description": "Container loaded successfully.", "properties": { "kind": { - "$ref": "#/$defs/ToolCallContributorKind.MCP" - }, - "customizationId": { - "type": "string", - "description": "Customization ID of the corresponding MCP server in {@link SessionState.customizations}." + "$ref": "#/$defs/CustomizationLoadStatus.Loaded" } }, "required": [ - "kind", - "customizationId" + "kind" ] }, - "ToolCallBase": { + "CustomizationDegradedState": { "type": "object", - "description": "Metadata common to all tool call states.", + "description": "Container partially loaded but has warnings.", "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" - }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Degraded" }, - "displayName": { + "message": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "description": "Human-readable description of the warning." } }, "required": [ - "toolCallId", - "toolName", - "displayName" + "kind", + "message" ] }, - "ToolCallParameterFields": { + "CustomizationErrorState": { "type": "object", - "description": "Properties available once tool call parameters are fully received.", + "description": "Container failed to load.", "properties": { - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "kind": { + "$ref": "#/$defs/CustomizationLoadStatus.Error" }, - "toolInput": { + "message": { "type": "string", - "description": "Raw tool input" + "description": "Human-readable error message." } }, "required": [ - "invocationMessage" + "kind", + "message" ] }, - "ToolCallResult": { + "ContainerCustomizationBase": { "type": "object", - "description": "Tool execution result details, available after execution completes.", + "description": "Fields shared by container customizations.", "properties": { - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "content": { + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } - }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" - } - }, - "required": [ - "success", - "pastTenseMessage" - ] - }, - "ToolCallStreamingState": { - "type": "object", - "description": "LM is streaming the tool call parameters.", - "properties": { - "toolCallId": { - "type": "string", - "description": "Unique tool call identifier" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "displayName": { + "clientId": { "type": "string", - "description": "Human-readable tool name" - }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." - }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Streaming" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "partialInput": { - "type": "string", - "description": "Partial parameters accumulated so far" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Progress message shown while parameters are streaming" + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/ChildCustomization" + }, + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." } }, - "required": [ - "toolCallId", - "toolName", - "displayName", - "status" + "required": [ + "id", + "uri", + "name", + "enabled" ] }, - "ToolCallPendingConfirmationState": { + "PluginCustomization": { "type": "object", - "description": "Parameters are complete, or a running tool requires re-confirmation\n(e.g. a mid-execution permission check).", + "description": "An [Open Plugins](https://open-plugins.com/) plugin.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingConfirmation" - }, - "confirmationTitle": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Short title for the confirmation prompt (e.g. `\"Run in terminal\"`, `\"Write file\"`)" - }, - "edits": { - "type": "object", - "properties": { - "items": { - "type": "string" - } - }, - "required": [ - "items" - ], - "description": "File edits that this tool call will perform, for preview before confirmation" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "editable": { - "type": "boolean", - "description": "Whether the agent host allows the client to edit the tool's input parameters before confirming" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "options": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ConfirmationOption" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Options the server offers for this confirmation. When present, the client\nSHOULD render these instead of a plain approve/deny UI. Each option\nbelongs to a {@link ConfirmationOptionGroup} so the client can still\ncategorise the choices." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallRunningState": { + "ClientPluginCustomization": { "type": "object", - "description": "Tool is actively executing.", + "description": "A {@link PluginCustomization} as published by a client. Extends the\nserver-facing shape with an opaque `nonce` so the host can detect when\nthe client's view of a plugin has changed and re-parse only as needed.\n\nClients SHOULD include a `nonce`. Server-side fields like\n{@link ContainerCustomizationBase.children | `children`} and\n{@link ContainerCustomizationBase.load | `load`} are typically left\nabsent on publication and populated by the host when the resolved\nplugin appears in {@link SessionState.customizations}.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." }, - "toolInput": { + "clientId": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Running" - }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "content": { + "children": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/ChildCustomization" }, - "description": "Partial content produced while the tool is still executing.\n\nFor example, a terminal content block lets clients subscribe to live\noutput before the tool completes." + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Plugin" + }, + "nonce": { + "type": "string", + "description": "Opaque version token used by the host to detect changes." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "enabled", + "type" ] }, - "ToolCallPendingResultConfirmationState": { + "DirectoryCustomization": { "type": "object", - "description": "Tool finished executing, waiting for client to approve the result.", + "description": "A directory the host watches for this session.\n\nPresence in the customization list signals that the host may discover\ncustomizations from this directory. When `writable` is `true`, clients\nMAY persist new customizations into the directory using\n[`resourceWrite`](/reference/common#resourcewrite); the host will\nthen surface the resulting child via the customization actions.\n\nThe directory may not yet exist on disk.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Internal tool name (for debugging/logging)" + "description": "Human-readable name." }, - "displayName": { + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "enabled": { + "type": "boolean", + "description": "Whether this container is currently enabled." + }, + "clientId": { "type": "string", - "description": "Human-readable tool name" + "description": "`clientId` of the client that contributed this container. Absent for\nserver-originated entries." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "load": { + "$ref": "#/$defs/CustomizationLoadState", + "description": "Host-reported load state. Absent means the host has not yet reported\na load state for this container." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/ChildCustomization" + }, + "description": "Children discovered inside this container.\n\nAbsent means the host has not parsed this container yet. An empty\narray means the host parsed the container and it contributes\nnothing." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "type": { + "$ref": "#/$defs/CustomizationType.Directory" }, - "toolInput": { + "contents": { + "$ref": "#/$defs/ChildCustomizationType", + "description": "Which child customization type this directory holds." + }, + "writable": { + "type": "boolean", + "description": "Whether clients may write into this directory." + } + }, + "required": [ + "id", + "uri", + "name", + "enabled", + "type", + "contents", + "writable" + ] + }, + "AgentCustomization": { + "type": "object", + "description": "A custom agent contributed by a plugin or directory.\n\nMirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents)\nformat: a markdown file with YAML frontmatter, where the body is the\nagent's system prompt.", + "properties": { + "id": { "type": "string", - "description": "Raw tool input" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "success": { - "type": "boolean", - "description": "Whether the tool succeeded" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "name": { + "type": "string", + "description": "Human-readable name." }, - "content": { + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" + "$ref": "#/$defs/Icon" }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." + "description": "Icons for UI display." }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } - }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.PendingResultConfirmation" + "type": { + "$ref": "#/$defs/CustomizationType.Agent" }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "description": { + "type": "string", + "description": "Short description of what the agent specializes in and when to\ninvoke it. Sourced from the agent file's frontmatter `description`." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "_meta": { + "type": "object", + "additionalProperties": {}, + "description": "Additional provider-specific metadata for this custom agent.\n\nMirrors the MCP `_meta` convention." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "type" ] }, - "ToolCallCompletedState": { + "SkillCustomization": { "type": "object", - "description": "Tool completed successfully or with an error.", + "description": "A skill contributed by a plugin or directory.\n\nCovers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills)\n— the `skills/` directory layout (one subdirectory per skill, each with\na `SKILL.md`) and the flatter `commands/` directory of slash-command\nskills.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "type": { + "$ref": "#/$defs/CustomizationType.Skill" }, - "toolInput": { + "description": { "type": "string", - "description": "Raw tool input" + "description": "Short description used for help text and auto-invocation matching.\nSourced from the skill's frontmatter `description`." }, - "success": { + "disableModelInvocation": { "type": "boolean", - "description": "Whether the tool succeeded" + "description": "When `true`, only the user can invoke this skill — the agent will not\nauto-invoke it. Sourced from the command skill's frontmatter\n`disable-model-invocation` flag." + } + }, + "required": [ + "id", + "uri", + "name", + "type" + ] + }, + "PromptCustomization": { + "type": "object", + "description": "A prompt contributed by a plugin or directory.", + "properties": { + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "pastTenseMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Past-tense description of what the tool did" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "content": { + "name": { + "type": "string", + "description": "Human-readable name." + }, + "icons": { "type": "array", "items": { - "$ref": "#/$defs/ToolResultContent" - }, - "description": "Unstructured result content blocks.\n\nThis mirrors the `content` field of MCP `CallToolResult`." - }, - "structuredContent": { - "type": "object", - "additionalProperties": {}, - "description": "Optional structured result object.\n\nThis mirrors the `structuredContent` field of MCP `CallToolResult`." - }, - "error": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "code": { - "type": "string" - } + "$ref": "#/$defs/Icon" }, - "required": [ - "message" - ], - "description": "Error details if the tool failed" + "description": "Icons for UI display." }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Completed" + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "confirmed": { - "$ref": "#/$defs/ToolCallConfirmationReason", - "description": "How the tool was confirmed for execution" + "type": { + "$ref": "#/$defs/CustomizationType.Prompt" }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "description": { + "type": "string", + "description": "Short description of what the prompt does." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "success", - "pastTenseMessage", - "status", - "confirmed" + "id", + "uri", + "name", + "type" ] }, - "ToolCallCancelledState": { + "RuleCustomization": { "type": "object", - "description": "Tool call was cancelled before execution.", + "description": "A rule contributed by a plugin or directory.\n\nMirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules)\nformat: a markdown file (e.g. `.mdc`) whose body is injected into\ncontext while the rule is active. This type also covers tool-specific\n\"instruction\" formats (e.g. VS Code Copilot's\n`.github/instructions/*.md`), which differ only in naming — they\nshare the same semantics of `description`, optional always-on\nactivation, and optional glob scoping.", "properties": { - "toolCallId": { + "id": { "type": "string", - "description": "Unique tool call identifier" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "toolName": { - "type": "string", - "description": "Internal tool name (for debugging/logging)" + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." }, - "displayName": { + "name": { "type": "string", - "description": "Human-readable tool name" + "description": "Human-readable name." }, - "contributor": { - "$ref": "#/$defs/ToolCallContributor", - "description": "Reference to the contributor of the tool being called." + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." }, - "_meta": { - "type": "object", - "additionalProperties": {}, - "description": "Additional provider-specific metadata for this tool call.\n\nThis MAY include a `ui` field corresponding to the MCP Apps (SEP-1865)\n`McpUiToolMeta` found in MCP tool calls, which may be used in combination\nwith the {@link contributor} to serve MCP Apps." + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." }, - "invocationMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Message describing what the tool will do" + "type": { + "$ref": "#/$defs/CustomizationType.Rule" }, - "toolInput": { + "description": { "type": "string", - "description": "Raw tool input" - }, - "status": { - "$ref": "#/$defs/ToolCallStatus.Cancelled" - }, - "reason": { - "$ref": "#/$defs/ToolCallCancellationReason", - "description": "Why the tool was cancelled" - }, - "reasonMessage": { - "$ref": "#/$defs/StringOrMarkdown", - "description": "Optional message explaining the cancellation" + "description": "Description of what the rule enforces." }, - "userSuggestion": { - "$ref": "#/$defs/Message", - "description": "What the user suggested doing instead" + "alwaysApply": { + "type": "boolean", + "description": "When `true`, the rule is always active (subject to `globs` if any).\nWhen `false` or absent, the agent or user decides whether to apply\nthe rule." }, - "selectedOption": { - "$ref": "#/$defs/ConfirmationOption", - "description": "The confirmation option the user selected, if confirmation options were provided" + "globs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Glob patterns the rule applies to. When present, the rule is only\nactive for matching files." } }, "required": [ - "toolCallId", - "toolName", - "displayName", - "invocationMessage", - "status", - "reason" + "id", + "uri", + "name", + "type" ] }, - "ToolResultTextContent": { + "HookCustomization": { "type": "object", - "description": "Text content in a tool result.\n\nMirrors MCP `TextContent`.", + "description": "A hook manifest contributed by a plugin or directory.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Text" + "id": { + "type": "string", + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "text": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "The text content" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.Hook" } }, "required": [ - "type", - "text" + "id", + "uri", + "name", + "type" ] }, - "ToolResultEmbeddedResourceContent": { + "McpServerCustomization": { "type": "object", - "description": "Base64-encoded binary content embedded in a tool result.\n\nMirrors MCP `EmbeddedResource` for inline binary data.", + "description": "An MCP server contributed by a plugin or directory.\n\nWhen the server is declared inline in the containing plugin manifest,\n`uri` points at the manifest file and\n{@link CustomizationBase.range | `range`} narrows it to the\ndeclaration's span.\n\nThe MCP server customization also reflects its current status.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.EmbeddedResource" - }, - "data": { + "id": { "type": "string", - "description": "Base64-encoded data" + "description": "Session-unique opaque identifier. Used by every action that targets a\nspecific customization. Minted by whoever publishes the customization\n(typically the agent host)." }, - "contentType": { + "uri": { + "$ref": "#/$defs/URI", + "description": "Source URI for this customization. A plugin URL, a file URI, or a\ndirectory URI.\n\nFor declarations that live inside a larger file — e.g. an MCP\nserver declared inline in a `plugins.json` manifest — `uri` points\nto the containing file and {@link CustomizationBase.range | `range`}\nnarrows it to the declaration's span." + }, + "name": { "type": "string", - "description": "Content type (e.g. `\"image/png\"`, `\"application/pdf\"`)" + "description": "Human-readable name." + }, + "icons": { + "type": "array", + "items": { + "$ref": "#/$defs/Icon" + }, + "description": "Icons for UI display." + }, + "range": { + "$ref": "#/$defs/TextRange", + "description": "Optional span within {@link CustomizationBase.uri | `uri`} when this\ncustomization is a subset of a larger file (for example, one entry\nin an inline `mcpServers` block of a `plugins.json` manifest).\nAbsent when the customization covers the whole resource." + }, + "type": { + "$ref": "#/$defs/CustomizationType.McpServer" + }, + "enabled": { + "type": "boolean", + "description": "Whether this MCP server is currently enabled." + }, + "state": { + "$ref": "#/$defs/McpServerState", + "description": "Current lifecycle state of the MCP server." + }, + "channel": { + "$ref": "#/$defs/URI", + "description": "An `mcp://`-protocol channel the client uses to side-channel traffic\ninto the upstream MCP server itself. The channel is NOT a fresh raw MCP\nconnection: it piggybacks on the AHP transport\nand skips the MCP `initialize` sequence.\n\nThe agent host MAY only serve a subset of MCP on this\nchannel; the served subset is described by domain-specific\ncapabilities such as those in\n{@link McpServerCustomizationApps.capabilities}.\n\nThe channel URI SHOULD be stable across the server's lifetime, but\nthe agent host MAY change it (for example across a restart) and\nMAY only expose it while the server is in\n{@link McpServerStatus.Ready | `Ready`}. Absence means no\nside-channel is currently available." + }, + "mcpApp": { + "$ref": "#/$defs/McpServerCustomizationApps", + "description": "MCP App support. This property SHOULD be advertised for MCP servers\nwhich support apps." } }, "required": [ + "id", + "uri", + "name", "type", - "data", - "contentType" + "enabled", + "state" ] }, - "ToolResultResourceContent": { + "McpServerCustomizationApps": { "type": "object", - "description": "A reference to a resource stored outside the tool result.\n\nWraps {@link ContentRef} for lazy-loading large results.", + "description": "Information from the agent host needed to render MCP Apps served\nby this MCP server.", "properties": { - "uri": { - "$ref": "#/$defs/URI", - "description": "Content URI" - }, - "sizeHint": { - "type": "number", - "description": "Approximate size in bytes" - }, - "contentType": { - "type": "string", - "description": "Content MIME type" - }, - "type": { - "$ref": "#/$defs/ToolResultContentType.Resource" + "capabilities": { + "$ref": "#/$defs/AhpMcpUiHostCapabilities", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nthe AHP host can satisfy for Views backed by this server. The\nclient feeds these straight through into the `hostCapabilities` of\nthe `ui/initialize` response delivered to the View." } }, "required": [ - "uri", - "type" + "capabilities" ] }, - "ToolResultFileEditContent": { + "AhpMcpUiHostCapabilities": { "type": "object", - "description": "Describes a file modification performed by a tool.", + "description": "The subset of MCP App\n[`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)\nan AHP host can derive from the upstream MCP server (and from AHP's own\nforwarding plumbing). Advertised on\n{@link McpServerCustomizationApps.capabilities} so clients can pass it\nthrough into the `hostCapabilities` of the `ui/initialize` response\ndelivered to an MCP App View.\n\nField names mirror the MCP Apps spec exactly, so the AHP-side producer\ncan pass them straight through into the `hostCapabilities` of the\n`ui/initialize` response delivered to the View.\n\nCapabilities outside this set (`openLinks`, `downloadFile`, `sandbox`,\n`experimental`) are decided locally by whichever AHP client renders the\nView and are NOT part of this AHP-level advertisement — only the\nserver-derived subset is.\n\nAn agent host MUST only advertise a capability when it actually accepts the\ncorresponding methods/notifications on the `mcp://` channel:\n\n- {@link serverTools}: host proxies `tools/list` and `tools/call` to\n the MCP server. When `listChanged` is `true`, the host also forwards\n `notifications/tools/list_changed`.\n- {@link serverResources}: host proxies `resources/read`,\n `resources/list`, and `resources/templates/list` to the MCP server.\n When `listChanged` is `true`, the host also forwards\n `notifications/resources/list_changed`.\n- {@link logging}: host accepts `notifications/message` log entries\n from the App and forwards them via `mcpNotification` (and forwards\n `logging/setLevel` calls to the server).\n- {@link sampling}: host serves `sampling/createMessage` via\n `mcpMethodCall`. When `sampling.tools` is present, the host also\n accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks\n inside `CreateMessageRequest`.", "properties": { - "before": { + "serverTools": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state before the edit. Absent for file creations or for in-place file edits." + "description": "Producer proxies the MCP `tools/*` methods to the upstream server." }, - "after": { + "serverResources": { "type": "object", "properties": { - "uri": { - "type": "string" - }, - "content": { - "type": "string" + "listChanged": { + "type": "boolean" } }, - "required": [ - "uri", - "content" - ], - "description": "The file state after the edit. Absent for file deletions." + "description": "Producer proxies the MCP `resources/*` methods to the upstream server." }, - "diff": { + "logging": { + "type": "object", + "additionalProperties": {}, + "description": "Producer accepts `notifications/message` log entries from the App via `mcpNotification`." + }, + "sampling": { "type": "object", "properties": { - "added": { - "type": "number" - }, - "removed": { - "type": "number" + "tools": { + "type": "string" } }, - "description": "Optional diff display metadata" - }, - "type": { - "$ref": "#/$defs/ToolResultContentType.FileEdit" + "description": "Producer serves `sampling/createMessage` via `mcpMethodCall`." + } + } + }, + "McpServerStartingState": { + "type": "object", + "description": "Server is registered with the host but has not yet started.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Starting" } }, "required": [ - "type" + "kind" ] }, - "ToolResultTerminalContent": { + "McpServerReadyState": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "Server is running and serving requests.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Terminal" + "kind": { + "$ref": "#/$defs/McpServerStatus.Ready" + } + }, + "required": [ + "kind" + ] + }, + "McpServerAuthRequiredState": { + "type": "object", + "description": "Server is reachable but cannot serve requests until the client\nauthenticates. Mirrors the discovery flow defined by\n[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)\n(Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge\nsemantics required by the MCP authorization spec.\n\nClients react to this state by calling the existing `authenticate`\ncommand with the {@link ProtectedResourceMetadata.resource | resource}\ncarried here. There is **no** `notify/authRequired` notification for\nMCP servers — the action stream is the single source of truth.\n\nWhen the transition is triggered by a request issued during a turn\n— most commonly\n{@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`}\nsurfacing mid-tool-call — the host SHOULD also raise\n{@link SessionStatus.InputNeeded} on the session so the block is\nvisible at the summary level. Clients SHOULD watch this status on\nany MCP server backing a running tool call and surface an explicit\naffordance (e.g. a \"grant additional access\" prompt) tied to that\ntool call, rather than relying on the user to notice the\ncustomization’s status badge.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.AuthRequired" + }, + "reason": { + "$ref": "#/$defs/McpAuthRequiredReason", + "description": "Why authentication is required." }, "resource": { - "$ref": "#/$defs/URI", - "description": "Terminal URI (subscribable for full terminal state)" + "$ref": "#/$defs/ProtectedResourceMetadata", + "description": "RFC 9728 Protected Resource Metadata. The `resource` field is the\ncanonical MCP server URI per RFC 8707, used as the OAuth `resource`\nindicator. `authorization_servers` is REQUIRED by the MCP\nauthorization spec." }, - "title": { + "requiredScopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Scopes required for the current challenge, parsed from the\n`WWW-Authenticate: Bearer scope=\"…\"` header (or `scopes_supported`\nfallback). Authoritative for the next authorization request — clients\nMUST NOT assume any subset/superset relationship to\n`resource.scopes_supported`." + }, + "description": { "type": "string", - "description": "Display title for the terminal content" + "description": "Human-readable hint, typically from the OAuth `error_description`." + } + }, + "required": [ + "kind", + "reason", + "resource" + ] + }, + "McpServerErrorState": { + "type": "object", + "description": "Server failed to start, crashed, or otherwise transitioned to a\nnon-recoverable error. Use {@link McpServerStatus.AuthRequired}\nfor authentication failures.", + "properties": { + "kind": { + "$ref": "#/$defs/McpServerStatus.Error" + }, + "error": { + "$ref": "#/$defs/ErrorInfo", + "description": "Error details." } }, "required": [ - "type", - "resource", - "title" + "kind", + "error" ] }, - "ToolResultSubagentContent": { + "McpServerStoppedState": { "type": "object", - "description": "A reference to a subagent session spawned by a tool.\n\nClients can subscribe to the subagent's session URI to stream its\nprogress in real time, including inner tool calls and responses.", + "description": "Server has been shut down. The host MAY remove the server from the\nsession entirely shortly after this state.", "properties": { - "type": { - "$ref": "#/$defs/ToolResultContentType.Subagent" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "Subagent session URI (subscribable for full session state)" - }, - "title": { - "type": "string", - "description": "Display title for the subagent" - }, - "agentName": { - "type": "string", - "description": "Internal agent name" - }, - "description": { - "type": "string", - "description": "Human-readable description of the subagent's task" + "kind": { + "$ref": "#/$defs/McpServerStatus.Stopped" } }, "required": [ - "type", - "resource", - "title" + "kind" ] }, "TerminalInfo": { @@ -4055,307 +3941,253 @@ ], "description": "A string that may optionally be rendered as Markdown.\n\n- A plain `string` is rendered as-is (no Markdown processing).\n- An object with `{ markdown: string }` is rendered with Markdown formatting." }, - "ChildCustomizationType": { + "SessionInputQuestion": { "oneOf": [ - {}, - { - "$ref": "#/$defs/CustomizationType.Agent" - }, { - "$ref": "#/$defs/CustomizationType.Skill" + "$ref": "#/$defs/SessionInputTextQuestion" }, { - "$ref": "#/$defs/CustomizationType.Prompt" + "$ref": "#/$defs/SessionInputNumberQuestion" }, { - "$ref": "#/$defs/CustomizationType.Rule" + "$ref": "#/$defs/SessionInputBooleanQuestion" }, { - "$ref": "#/$defs/CustomizationType.Hook" + "$ref": "#/$defs/SessionInputSingleSelectQuestion" }, { - "$ref": "#/$defs/CustomizationType.McpServer" + "$ref": "#/$defs/SessionInputMultiSelectQuestion" } ], - "description": "Customization types that appear as children of a\n{@link PluginCustomization} or {@link DirectoryCustomization}." + "description": "One question within a session input request." }, - "CustomizationLoadState": { + "SessionInputAnswerValue": { "oneOf": [ - {}, { - "$ref": "#/$defs/CustomizationLoadingState" + "$ref": "#/$defs/SessionInputTextAnswerValue" }, { - "$ref": "#/$defs/CustomizationLoadedState" + "$ref": "#/$defs/SessionInputNumberAnswerValue" }, { - "$ref": "#/$defs/CustomizationDegradedState" + "$ref": "#/$defs/SessionInputBooleanAnswerValue" }, { - "$ref": "#/$defs/CustomizationErrorState" + "$ref": "#/$defs/SessionInputSelectedAnswerValue" + }, + { + "$ref": "#/$defs/SessionInputSelectedManyAnswerValue" } - ], - "description": "Discriminated load state for a container customization\n({@link PluginCustomization} or {@link DirectoryCustomization})." + ] }, - "ChildCustomization": { + "SessionInputAnswer": { "oneOf": [ - {}, - { - "$ref": "#/$defs/AgentCustomization" - }, - { - "$ref": "#/$defs/SkillCustomization" - }, - { - "$ref": "#/$defs/PromptCustomization" - }, { - "$ref": "#/$defs/RuleCustomization" - }, - { - "$ref": "#/$defs/HookCustomization" + "$ref": "#/$defs/SessionInputAnswered" }, { - "$ref": "#/$defs/McpServerCustomization" + "$ref": "#/$defs/SessionInputSkipped" } ], - "description": "Child customizations that live inside a {@link PluginCustomization} or\n{@link DirectoryCustomization}." + "description": "Draft, submitted, or skipped answer for one question." }, - "Customization": { + "MessageAttachment": { "oneOf": [ {}, { - "$ref": "#/$defs/PluginCustomization" + "$ref": "#/$defs/SimpleMessageAttachment" }, { - "$ref": "#/$defs/DirectoryCustomization" + "$ref": "#/$defs/MessageEmbeddedResourceAttachment" }, { - "$ref": "#/$defs/McpServerCustomization" + "$ref": "#/$defs/MessageResourceAttachment" + }, + { + "$ref": "#/$defs/MessageAnnotationsAttachment" } ], - "description": "A top-level customization active in a session. Either a container\n({@link PluginCustomization} or {@link DirectoryCustomization}) whose\nleaf customizations live in its\n{@link ContainerCustomizationBase.children | `children`} array, or a\nbare {@link McpServerCustomization} surfaced directly by the host." + "description": "An attachment associated with a {@link Message}." }, - "McpServerState": { + "ResponsePart": { "oneOf": [ {}, { - "$ref": "#/$defs/McpServerStartingState" + "$ref": "#/$defs/MarkdownResponsePart" }, { - "$ref": "#/$defs/McpServerReadyState" + "$ref": "#/$defs/ResourceReponsePart" }, { - "$ref": "#/$defs/McpServerAuthRequiredState" + "$ref": "#/$defs/ToolCallResponsePart" }, { - "$ref": "#/$defs/McpServerErrorState" + "$ref": "#/$defs/ReasoningResponsePart" }, { - "$ref": "#/$defs/McpServerStoppedState" + "$ref": "#/$defs/SystemNotificationResponsePart" } - ], - "description": "Discriminated union of all MCP server lifecycle states.\nDiscriminated by `kind` (a {@link McpServerStatus} value)." + ] }, - "ChatOrigin": { + "ToolCallContributor": { "oneOf": [ - {}, - { - "type": "object", - "properties": { - "kind": { - "type": "string" - } - }, - "required": [ - "kind" - ] - }, { - "type": "object", - "properties": { - "kind": { - "type": "string" - }, - "chat": { - "type": "string" - }, - "turnId": { - "type": "string" - } - }, - "required": [ - "kind", - "chat", - "turnId" - ] + "$ref": "#/$defs/ToolCallClientContributor" }, { - "type": "object", - "properties": { - "kind": { - "type": "string" - }, - "chat": { - "type": "string" - }, - "toolCallId": { - "type": "string" - } - }, - "required": [ - "kind", - "chat", - "toolCallId" - ] + "$ref": "#/$defs/ToolCallMcpContributor" } ] }, - "ChatInputQuestion": { + "ToolCallState": { "oneOf": [ + {}, { - "$ref": "#/$defs/ChatInputTextQuestion" + "$ref": "#/$defs/ToolCallStreamingState" + }, + { + "$ref": "#/$defs/ToolCallPendingConfirmationState" }, { - "$ref": "#/$defs/ChatInputNumberQuestion" + "$ref": "#/$defs/ToolCallRunningState" }, { - "$ref": "#/$defs/ChatInputBooleanQuestion" + "$ref": "#/$defs/ToolCallPendingResultConfirmationState" }, { - "$ref": "#/$defs/ChatInputSingleSelectQuestion" + "$ref": "#/$defs/ToolCallCompletedState" }, { - "$ref": "#/$defs/ChatInputMultiSelectQuestion" + "$ref": "#/$defs/ToolCallCancelledState" } ], - "description": "One question within a chat input request." + "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." }, - "ChatInputAnswerValue": { + "ToolResultContent": { "oneOf": [ + {}, { - "$ref": "#/$defs/ChatInputTextAnswerValue" + "$ref": "#/$defs/ToolResultTextContent" }, { - "$ref": "#/$defs/ChatInputNumberAnswerValue" + "$ref": "#/$defs/ToolResultEmbeddedResourceContent" }, { - "$ref": "#/$defs/ChatInputBooleanAnswerValue" + "$ref": "#/$defs/ToolResultResourceContent" }, { - "$ref": "#/$defs/ChatInputSelectedAnswerValue" + "$ref": "#/$defs/ToolResultFileEditContent" }, { - "$ref": "#/$defs/ChatInputSelectedManyAnswerValue" - } - ] - }, - "ChatInputAnswer": { - "oneOf": [ - { - "$ref": "#/$defs/ChatInputAnswered" + "$ref": "#/$defs/ToolResultTerminalContent" }, { - "$ref": "#/$defs/ChatInputSkipped" + "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Draft, submitted, or skipped answer for one question." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output, and\n`ToolResultSubagentContent` for subagent sessions (AHP extensions)." }, - "MessageAttachment": { + "ChildCustomizationType": { "oneOf": [ {}, { - "$ref": "#/$defs/SimpleMessageAttachment" + "$ref": "#/$defs/CustomizationType.Agent" }, { - "$ref": "#/$defs/MessageEmbeddedResourceAttachment" + "$ref": "#/$defs/CustomizationType.Skill" }, { - "$ref": "#/$defs/MessageResourceAttachment" + "$ref": "#/$defs/CustomizationType.Prompt" }, { - "$ref": "#/$defs/MessageAnnotationsAttachment" + "$ref": "#/$defs/CustomizationType.Rule" + }, + { + "$ref": "#/$defs/CustomizationType.Hook" + }, + { + "$ref": "#/$defs/CustomizationType.McpServer" } ], - "description": "An attachment associated with a {@link Message}." + "description": "Customization types that appear as children of a\n{@link PluginCustomization} or {@link DirectoryCustomization}." }, - "ResponsePart": { + "CustomizationLoadState": { "oneOf": [ {}, { - "$ref": "#/$defs/MarkdownResponsePart" - }, - { - "$ref": "#/$defs/ResourceReponsePart" - }, - { - "$ref": "#/$defs/ToolCallResponsePart" + "$ref": "#/$defs/CustomizationLoadingState" }, { - "$ref": "#/$defs/ReasoningResponsePart" + "$ref": "#/$defs/CustomizationLoadedState" }, { - "$ref": "#/$defs/SystemNotificationResponsePart" - } - ] - }, - "ToolCallContributor": { - "oneOf": [ - { - "$ref": "#/$defs/ToolCallClientContributor" + "$ref": "#/$defs/CustomizationDegradedState" }, { - "$ref": "#/$defs/ToolCallMcpContributor" + "$ref": "#/$defs/CustomizationErrorState" } - ] + ], + "description": "Discriminated load state for a container customization\n({@link PluginCustomization} or {@link DirectoryCustomization})." }, - "ToolCallState": { + "ChildCustomization": { "oneOf": [ {}, { - "$ref": "#/$defs/ToolCallStreamingState" + "$ref": "#/$defs/AgentCustomization" }, { - "$ref": "#/$defs/ToolCallPendingConfirmationState" + "$ref": "#/$defs/SkillCustomization" }, { - "$ref": "#/$defs/ToolCallRunningState" + "$ref": "#/$defs/PromptCustomization" }, { - "$ref": "#/$defs/ToolCallPendingResultConfirmationState" + "$ref": "#/$defs/RuleCustomization" }, { - "$ref": "#/$defs/ToolCallCompletedState" + "$ref": "#/$defs/HookCustomization" }, { - "$ref": "#/$defs/ToolCallCancelledState" + "$ref": "#/$defs/McpServerCustomization" } ], - "description": "Discriminated union of all tool call lifecycle states.\n\nSee the [state model guide](/guide/state-model.html#tool-call-lifecycle)\nfor the full state machine diagram." + "description": "Child customizations that live inside a {@link PluginCustomization} or\n{@link DirectoryCustomization}." }, - "ToolResultContent": { + "Customization": { "oneOf": [ {}, { - "$ref": "#/$defs/ToolResultTextContent" + "$ref": "#/$defs/PluginCustomization" }, { - "$ref": "#/$defs/ToolResultEmbeddedResourceContent" + "$ref": "#/$defs/DirectoryCustomization" }, { - "$ref": "#/$defs/ToolResultResourceContent" + "$ref": "#/$defs/McpServerCustomization" + } + ], + "description": "A top-level customization active in a session. Either a container\n({@link PluginCustomization} or {@link DirectoryCustomization}) whose\nleaf customizations live in its\n{@link ContainerCustomizationBase.children | `children`} array, or a\nbare {@link McpServerCustomization} surfaced directly by the host." + }, + "McpServerState": { + "oneOf": [ + {}, + { + "$ref": "#/$defs/McpServerStartingState" }, { - "$ref": "#/$defs/ToolResultFileEditContent" + "$ref": "#/$defs/McpServerReadyState" }, { - "$ref": "#/$defs/ToolResultTerminalContent" + "$ref": "#/$defs/McpServerAuthRequiredState" }, { - "$ref": "#/$defs/ToolResultSubagentContent" + "$ref": "#/$defs/McpServerErrorState" + }, + { + "$ref": "#/$defs/McpServerStoppedState" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output, and\n`ToolResultSubagentContent` for subagent sessions (AHP extensions)." + "description": "Discriminated union of all MCP server lifecycle states.\nDiscriminated by `kind` (a {@link McpServerStatus} value)." }, "TerminalClaim": { "oneOf": [ diff --git a/scripts/find-protocol-sources.ts b/scripts/find-protocol-sources.ts index 862088bd..40777394 100644 --- a/scripts/find-protocol-sources.ts +++ b/scripts/find-protocol-sources.ts @@ -18,7 +18,6 @@ export const PROTOCOL_SOURCE_DIRS: readonly string[] = [ 'common', 'channels-root', 'channels-session', - 'channels-chat', 'channels-terminal', 'channels-changeset', 'channels-annotations', diff --git a/scripts/generate-action-origin.ts b/scripts/generate-action-origin.ts index 0b66aa73..b641f496 100644 --- a/scripts/generate-action-origin.ts +++ b/scripts/generate-action-origin.ts @@ -17,7 +17,7 @@ const GENERATED_HEADER = `// Generated from types/actions.ts — do not edit // Run \`npm run generate\` to regenerate. `; -type ActionScope = 'root' | 'session' | 'chat' | 'terminal' | 'changeset' | 'annotations' | 'resourceWatch'; +type ActionScope = 'root' | 'session' | 'terminal' | 'changeset' | 'annotations' | 'resourceWatch'; interface ActionInfo { /** The interface name (e.g. 'RootAgentsChangedAction') */ @@ -150,7 +150,6 @@ export function generateActionOrigin(project: Project, outDir: string): void { const category = getJsDocTag(node as any, 'category') || ''; const scope: ActionScope = category === 'Root Actions' ? 'root' - : category === 'Chat Actions' ? 'chat' : category === 'Terminal Actions' ? 'terminal' : category === 'Changeset Actions' ? 'changeset' : category === 'Annotations Actions' ? 'annotations' @@ -202,7 +201,6 @@ export function generateActionOrigin(project: Project, outDir: string): void { // Generate output const rootActions = actions.filter(a => a.scope === 'root'); const sessionActions = actions.filter(a => a.scope === 'session'); - const chatActions = actions.filter(a => a.scope === 'chat'); const terminalActions = actions.filter(a => a.scope === 'terminal'); const changesetActions = actions.filter(a => a.scope === 'changeset'); const annotationsActions = actions.filter(a => a.scope === 'annotations'); @@ -211,8 +209,6 @@ export function generateActionOrigin(project: Project, outDir: string): void { const serverRootActions = rootActions.filter(a => !a.isClientDispatchable); const clientSessionActions = sessionActions.filter(a => a.isClientDispatchable); const serverSessionActions = sessionActions.filter(a => !a.isClientDispatchable); - const clientChatActions = chatActions.filter(a => a.isClientDispatchable); - const serverChatActions = chatActions.filter(a => !a.isClientDispatchable); const clientTerminalActions = terminalActions.filter(a => a.isClientDispatchable); const serverTerminalActions = terminalActions.filter(a => !a.isClientDispatchable); const clientChangesetActions = changesetActions.filter(a => a.isClientDispatchable); @@ -236,7 +232,7 @@ export function generateActionOrigin(project: Project, outDir: string): void { lines.push(``); // RootAction - lines.push(`// ─── Root vs Session vs Chat vs Terminal vs Changeset Action Unions ─────────────────`); + lines.push(`// ─── Root vs Session vs Terminal vs Changeset Action Unions ─────────────────`); lines.push(``); lines.push(`/** Union of all root-scoped actions. */`); lines.push(`export type RootAction =`); @@ -291,33 +287,6 @@ export function generateActionOrigin(project: Project, outDir: string): void { lines.push(`;`); lines.push(``); - // ChatAction - lines.push(`/** Union of all chat-scoped actions. */`); - lines.push(`export type ChatAction =`); - for (let i = 0; i < chatActions.length; i++) { - lines.push(` | ${chatActions[i].name}`); - } - lines.push(`;`); - lines.push(``); - - // ClientChatAction - lines.push(`/** Union of chat actions that clients may dispatch. */`); - lines.push(`export type ClientChatAction =`); - for (let i = 0; i < clientChatActions.length; i++) { - lines.push(` | ${clientChatActions[i].name}`); - } - lines.push(`;`); - lines.push(``); - - // ServerChatAction - lines.push(`/** Union of chat actions that only the server may produce. */`); - lines.push(`export type ServerChatAction =`); - for (let i = 0; i < serverChatActions.length; i++) { - lines.push(` | ${serverChatActions[i].name}`); - } - lines.push(`;`); - lines.push(``); - // TerminalAction lines.push(`/** Union of all terminal-scoped actions. */`); lines.push(`export type TerminalAction =`); diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 676030e2..b76d6182 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -162,11 +162,9 @@ function mapType(tsType: string): string { tsType === 'IRootState | ISessionState | ITerminalState' || tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState' || - tsType === 'RootState | SessionState | ChatState | TerminalState | ChangesetState' || - tsType === 'RootState | SessionState | ChatState | TerminalState | ChangesetState | AnnotationsState' + tsType === 'RootState | SessionState | TerminalState | ChangesetState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState' ) { return 'SnapshotState'; } @@ -634,9 +632,9 @@ function generateDiscriminatedUnion(cfg: UnionConfig): string { // ─── State File Generator ──────────────────────────────────────────────────── const STATE_ENUMS = [ - 'PolicyState', 'SessionLifecycle', 'SessionStatus', - 'ChatOriginKind', 'PendingMessageKind', 'ChatInputAnswerState', 'ChatInputAnswerValueKind', 'ChatInputQuestionKind', - 'ChatInputResponseKind', + 'PolicyState', 'PendingMessageKind', 'SessionLifecycle', 'SessionStatus', + 'SessionInputAnswerState', 'SessionInputAnswerValueKind', 'SessionInputQuestionKind', + 'SessionInputResponseKind', 'TurnState', 'MessageAttachmentKind', 'ResponsePartKind', 'ToolCallStatus', 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolCallContributorKind', @@ -656,13 +654,11 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'AgentSelection' }, { name: 'ConfigPropertySchema' }, { name: 'ConfigSchema' }, + { name: 'PendingMessage' }, { name: 'SessionState' }, { name: 'SessionActiveClient' }, { name: 'SessionSummary' }, { name: 'ChangesSummary' }, - { name: 'ChatState' }, - { name: 'ChatSummary' }, - { name: 'PendingMessage' }, { name: 'ProjectInfo' }, { name: 'SessionConfigPropertySchema' }, { name: 'SessionConfigSchema' }, @@ -670,20 +666,20 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'Turn' }, { name: 'ActiveTurn' }, { name: 'Message' }, - { name: 'ChatInputOption' }, - { name: 'ChatInputTextAnswerValue' }, - { name: 'ChatInputNumberAnswerValue' }, - { name: 'ChatInputBooleanAnswerValue' }, - { name: 'ChatInputSelectedAnswerValue' }, - { name: 'ChatInputSelectedManyAnswerValue' }, - { name: 'ChatInputAnswered' }, - { name: 'ChatInputSkipped' }, - { name: 'ChatInputTextQuestion' }, - { name: 'ChatInputNumberQuestion' }, - { name: 'ChatInputBooleanQuestion' }, - { name: 'ChatInputSingleSelectQuestion' }, - { name: 'ChatInputMultiSelectQuestion' }, - { name: 'ChatInputRequest' }, + { name: 'SessionInputOption' }, + { name: 'SessionInputTextAnswerValue' }, + { name: 'SessionInputNumberAnswerValue' }, + { name: 'SessionInputBooleanAnswerValue' }, + { name: 'SessionInputSelectedAnswerValue' }, + { name: 'SessionInputSelectedManyAnswerValue' }, + { name: 'SessionInputAnswered' }, + { name: 'SessionInputSkipped' }, + { name: 'SessionInputTextQuestion' }, + { name: 'SessionInputNumberQuestion' }, + { name: 'SessionInputBooleanQuestion' }, + { name: 'SessionInputSingleSelectQuestion' }, + { name: 'SessionInputMultiSelectQuestion' }, + { name: 'SessionInputRequest' }, { name: 'TextPosition' }, { name: 'TextRange' }, { name: 'TextSelection' }, @@ -809,43 +805,43 @@ const TERMINAL_CONTENT_PART_UNION: UnionConfig = { unknown: true, }; -const CHAT_INPUT_QUESTION_UNION: UnionConfig = { - name: 'ChatInputQuestion', +const SESSION_INPUT_QUESTION_UNION: UnionConfig = { + name: 'SessionInputQuestion', discriminantField: 'kind', - doc: 'ChatInputQuestion is one question within a chat input request.', + doc: 'SessionInputQuestion is one question within a session input request.', variants: [ - { variantName: 'Text', innerType: 'ChatInputTextQuestion', wireValue: 'text' }, - { variantName: 'Number', innerType: 'ChatInputNumberQuestion', wireValue: 'number' }, - { variantName: 'Integer', innerType: 'ChatInputNumberQuestion', wireValue: 'integer' }, - { variantName: 'Boolean', innerType: 'ChatInputBooleanQuestion', wireValue: 'boolean' }, - { variantName: 'SingleSelect', innerType: 'ChatInputSingleSelectQuestion', wireValue: 'single-select' }, - { variantName: 'MultiSelect', innerType: 'ChatInputMultiSelectQuestion', wireValue: 'multi-select' }, + { variantName: 'Text', innerType: 'SessionInputTextQuestion', wireValue: 'text' }, + { variantName: 'Number', innerType: 'SessionInputNumberQuestion', wireValue: 'number' }, + { variantName: 'Integer', innerType: 'SessionInputNumberQuestion', wireValue: 'integer' }, + { variantName: 'Boolean', innerType: 'SessionInputBooleanQuestion', wireValue: 'boolean' }, + { variantName: 'SingleSelect', innerType: 'SessionInputSingleSelectQuestion', wireValue: 'single-select' }, + { variantName: 'MultiSelect', innerType: 'SessionInputMultiSelectQuestion', wireValue: 'multi-select' }, ], unknown: true, }; -const CHAT_INPUT_ANSWER_VALUE_UNION: UnionConfig = { - name: 'ChatInputAnswerValue', +const SESSION_INPUT_ANSWER_VALUE_UNION: UnionConfig = { + name: 'SessionInputAnswerValue', discriminantField: 'kind', - doc: 'ChatInputAnswerValue is the value captured for one answer.', + doc: 'SessionInputAnswerValue is the value captured for one answer.', variants: [ - { variantName: 'Text', innerType: 'ChatInputTextAnswerValue', wireValue: 'text' }, - { variantName: 'Number', innerType: 'ChatInputNumberAnswerValue', wireValue: 'number' }, - { variantName: 'Boolean', innerType: 'ChatInputBooleanAnswerValue', wireValue: 'boolean' }, - { variantName: 'Selected', innerType: 'ChatInputSelectedAnswerValue', wireValue: 'selected' }, - { variantName: 'SelectedMany', innerType: 'ChatInputSelectedManyAnswerValue', wireValue: 'selected-many' }, + { variantName: 'Text', innerType: 'SessionInputTextAnswerValue', wireValue: 'text' }, + { variantName: 'Number', innerType: 'SessionInputNumberAnswerValue', wireValue: 'number' }, + { variantName: 'Boolean', innerType: 'SessionInputBooleanAnswerValue', wireValue: 'boolean' }, + { variantName: 'Selected', innerType: 'SessionInputSelectedAnswerValue', wireValue: 'selected' }, + { variantName: 'SelectedMany', innerType: 'SessionInputSelectedManyAnswerValue', wireValue: 'selected-many' }, ], unknown: true, }; -const CHAT_INPUT_ANSWER_UNION: UnionConfig = { - name: 'ChatInputAnswer', +const SESSION_INPUT_ANSWER_UNION: UnionConfig = { + name: 'SessionInputAnswer', discriminantField: 'state', - doc: 'ChatInputAnswer is a draft, submitted, or skipped answer for one question.', + doc: 'SessionInputAnswer is a draft, submitted, or skipped answer for one question.', variants: [ - { variantName: 'Draft', innerType: 'ChatInputAnswered', wireValue: 'draft' }, - { variantName: 'Submitted', innerType: 'ChatInputAnswered', wireValue: 'submitted' }, - { variantName: 'Skipped', innerType: 'ChatInputSkipped', wireValue: 'skipped' }, + { variantName: 'Draft', innerType: 'SessionInputAnswered', wireValue: 'draft' }, + { variantName: 'Submitted', innerType: 'SessionInputAnswered', wireValue: 'submitted' }, + { variantName: 'Skipped', innerType: 'SessionInputSkipped', wireValue: 'skipped' }, ], unknown: true, }; @@ -943,99 +939,15 @@ const TOOL_CALL_CONTRIBUTOR_UNION: UnionConfig = { unknown: true, }; -function generateChatOriginGo(): string { - return `// ChatOrigin describes how a chat came into existence. -type ChatOrigin struct { -\tValue isChatOrigin -} - -// isChatOrigin is the marker interface for chat origin variants. -type isChatOrigin interface{ isChatOrigin() } - -type ChatUserOrigin struct { -\tKind ChatOriginKind \`json:"kind"\` -} - -func (*ChatUserOrigin) isChatOrigin() {} - -type ChatForkOrigin struct { -\tKind ChatOriginKind \`json:"kind"\` -\tChat URI \`json:"chat"\` -\tTurnId string \`json:"turnId"\` -} - -func (*ChatForkOrigin) isChatOrigin() {} - -type ChatToolOrigin struct { -\tKind ChatOriginKind \`json:"kind"\` -\tChat URI \`json:"chat"\` -\tToolCallId string \`json:"toolCallId"\` -} - -func (*ChatToolOrigin) isChatOrigin() {} - -type ChatOriginUnknown struct { -\tRaw json.RawMessage -} - -func (*ChatOriginUnknown) isChatOrigin() {} - -func (o *ChatOrigin) UnmarshalJSON(data []byte) error { -\tdisc, _, err := readDiscriminator(data, "kind") -\tif err != nil { -\t\treturn err -\t} -\tswitch disc { -\tcase "user": -\t\tvar v ChatUserOrigin -\t\tif err := json.Unmarshal(data, &v); err != nil { -\t\t\treturn err -\t\t} -\t\to.Value = &v -\tcase "fork": -\t\tvar v ChatForkOrigin -\t\tif err := json.Unmarshal(data, &v); err != nil { -\t\t\treturn err -\t\t} -\t\to.Value = &v -\tcase "tool": -\t\tvar v ChatToolOrigin -\t\tif err := json.Unmarshal(data, &v); err != nil { -\t\t\treturn err -\t\t} -\t\to.Value = &v -\tdefault: -\t\traw := make(json.RawMessage, len(data)) -\t\tcopy(raw, data) -\t\to.Value = &ChatOriginUnknown{Raw: raw} -\t} -\treturn nil -} - -func (o ChatOrigin) MarshalJSON() ([]byte, error) { -\tif unk, ok := o.Value.(*ChatOriginUnknown); ok { -\t\tif len(unk.Raw) == 0 { -\t\t\treturn []byte("null"), nil -\t\t} -\t\treturn unk.Raw, nil -\t} -\tif o.Value == nil { -\t\treturn []byte("null"), nil -\t} -\treturn json.Marshal(o.Value) -}`; -} - function generateSnapshotState(): string { return `// SnapshotState is the state payload of a snapshot — root, session, -// chat, terminal, changeset, resource-watch, or annotations state. The active +// terminal, changeset, resource-watch, or annotations state. The active // variant is chosen by which pointer field is non-nil; UnmarshalJSON probes // for required fields in the canonical order -// (session → chat → terminal → changeset → resourceWatch → annotations → root). +// (session → terminal → changeset → resourceWatch → annotations → root). type SnapshotState struct { \tRoot *RootState \`json:"-"\` \tSession *SessionState \`json:"-"\` -\tChat *ChatState \`json:"-"\` \tTerminal *TerminalState \`json:"-"\` \tChangeset *ChangesetState \`json:"-"\` \tResourceWatch *ResourceWatchState \`json:"-"\` @@ -1047,8 +959,6 @@ func (s SnapshotState) MarshalJSON() ([]byte, error) { \tswitch { \tcase s.Session != nil: \t\treturn json.Marshal(s.Session) -\tcase s.Chat != nil: -\t\treturn json.Marshal(s.Chat) \tcase s.Terminal != nil: \t\treturn json.Marshal(s.Terminal) \tcase s.Changeset != nil: @@ -1079,12 +989,6 @@ func (s *SnapshotState) UnmarshalJSON(data []byte) error { \t\t\treturn err \t\t} \t\ts.Session = &v -\tcase containsAll(probe, "summary", "turns"): -\t\tvar v ChatState -\t\tif err := json.Unmarshal(data, &v); err != nil { -\t\t\treturn err -\t\t} -\t\ts.Chat = &v \tcase containsAll(probe, "content"): \t\tvar v TerminalState \t\tif err := json.Unmarshal(data, &v); err != nil { @@ -1165,11 +1069,11 @@ function generateStateFile(project: Project): string { lines.push(''); lines.push(generateDiscriminatedUnion(TERMINAL_CONTENT_PART_UNION)); lines.push(''); - lines.push(generateDiscriminatedUnion(CHAT_INPUT_QUESTION_UNION)); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_QUESTION_UNION)); lines.push(''); - lines.push(generateDiscriminatedUnion(CHAT_INPUT_ANSWER_VALUE_UNION)); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_ANSWER_VALUE_UNION)); lines.push(''); - lines.push(generateDiscriminatedUnion(CHAT_INPUT_ANSWER_UNION)); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_ANSWER_UNION)); lines.push(''); lines.push(generateDiscriminatedUnion(TOOL_RESULT_CONTENT_UNION)); lines.push(''); @@ -1185,8 +1089,6 @@ function generateStateFile(project: Project): string { lines.push(''); lines.push(generateDiscriminatedUnion(TOOL_CALL_CONTRIBUTOR_UNION)); lines.push(''); - lines.push(generateChatOriginGo()); - lines.push(''); lines.push(generateSnapshotState()); lines.push(''); @@ -1205,33 +1107,21 @@ const ACTION_VARIANTS: { { type: 'root/configChanged', variantName: 'RootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'session/ready', variantName: 'SessionReady', tsInterface: 'SessionReadyAction' }, { type: 'session/creationFailed', variantName: 'SessionCreationFailed', tsInterface: 'SessionCreationFailedAction' }, - { type: 'session/chatAdded', variantName: 'SessionChatAdded', tsInterface: 'SessionChatAddedAction' }, - { type: 'session/chatRemoved', variantName: 'SessionChatRemoved', tsInterface: 'SessionChatRemovedAction' }, - { type: 'session/chatUpdated', variantName: 'SessionChatUpdated', tsInterface: 'SessionChatUpdatedAction' }, - { type: 'session/defaultChatChanged', variantName: 'SessionDefaultChatChanged', tsInterface: 'SessionDefaultChatChangedAction' }, - { type: 'chat/turnStarted', variantName: 'ChatTurnStarted', tsInterface: 'ChatTurnStartedAction' }, - { type: 'chat/delta', variantName: 'ChatDelta', tsInterface: 'ChatDeltaAction' }, - { type: 'chat/responsePart', variantName: 'ChatResponsePart', tsInterface: 'ChatResponsePartAction' }, - { type: 'chat/toolCallStart', variantName: 'ChatToolCallStart', tsInterface: 'ChatToolCallStartAction' }, - { type: 'chat/toolCallDelta', variantName: 'ChatToolCallDelta', tsInterface: 'ChatToolCallDeltaAction' }, - { type: 'chat/toolCallReady', variantName: 'ChatToolCallReady', tsInterface: 'ChatToolCallReadyAction' }, - { type: 'chat/toolCallConfirmed', variantName: 'ChatToolCallConfirmed', tsInterface: '_chat_tool_call_confirmed_' }, - { type: 'chat/toolCallComplete', variantName: 'ChatToolCallComplete', tsInterface: 'ChatToolCallCompleteAction' }, - { type: 'chat/toolCallResultConfirmed', variantName: 'ChatToolCallResultConfirmed', tsInterface: 'ChatToolCallResultConfirmedAction' }, - { type: 'chat/toolCallContentChanged', variantName: 'ChatToolCallContentChanged', tsInterface: 'ChatToolCallContentChangedAction' }, - { type: 'chat/turnComplete', variantName: 'ChatTurnComplete', tsInterface: 'ChatTurnCompleteAction' }, - { type: 'chat/turnCancelled', variantName: 'ChatTurnCancelled', tsInterface: 'ChatTurnCancelledAction' }, - { type: 'chat/error', variantName: 'ChatError', tsInterface: 'ChatErrorAction' }, + { type: 'session/turnStarted', variantName: 'SessionTurnStarted', tsInterface: 'SessionTurnStartedAction' }, + { type: 'session/delta', variantName: 'SessionDelta', tsInterface: 'SessionDeltaAction' }, + { type: 'session/responsePart', variantName: 'SessionResponsePart', tsInterface: 'SessionResponsePartAction' }, + { type: 'session/toolCallStart', variantName: 'SessionToolCallStart', tsInterface: 'SessionToolCallStartAction' }, + { type: 'session/toolCallDelta', variantName: 'SessionToolCallDelta', tsInterface: 'SessionToolCallDeltaAction' }, + { type: 'session/toolCallReady', variantName: 'SessionToolCallReady', tsInterface: 'SessionToolCallReadyAction' }, + { type: 'session/toolCallConfirmed', variantName: 'SessionToolCallConfirmed', tsInterface: '_merged_' }, + { type: 'session/toolCallComplete', variantName: 'SessionToolCallComplete', tsInterface: 'SessionToolCallCompleteAction' }, + { type: 'session/toolCallResultConfirmed', variantName: 'SessionToolCallResultConfirmed', tsInterface: 'SessionToolCallResultConfirmedAction' }, + { type: 'session/turnComplete', variantName: 'SessionTurnComplete', tsInterface: 'SessionTurnCompleteAction' }, + { type: 'session/turnCancelled', variantName: 'SessionTurnCancelled', tsInterface: 'SessionTurnCancelledAction' }, + { type: 'session/error', variantName: 'SessionError', tsInterface: 'SessionErrorAction' }, { type: 'session/titleChanged', variantName: 'SessionTitleChanged', tsInterface: 'SessionTitleChangedAction' }, - { type: 'chat/usage', variantName: 'ChatUsage', tsInterface: 'ChatUsageAction' }, - { type: 'chat/reasoning', variantName: 'ChatReasoning', tsInterface: 'ChatReasoningAction' }, - { type: 'chat/pendingMessageSet', variantName: 'ChatPendingMessageSet', tsInterface: 'ChatPendingMessageSetAction' }, - { type: 'chat/pendingMessageRemoved', variantName: 'ChatPendingMessageRemoved', tsInterface: 'ChatPendingMessageRemovedAction' }, - { type: 'chat/queuedMessagesReordered', variantName: 'ChatQueuedMessagesReordered', tsInterface: 'ChatQueuedMessagesReorderedAction' }, - { type: 'chat/inputRequested', variantName: 'ChatInputRequested', tsInterface: 'ChatInputRequestedAction' }, - { type: 'chat/inputAnswerChanged', variantName: 'ChatInputAnswerChanged', tsInterface: 'ChatInputAnswerChangedAction' }, - { type: 'chat/inputCompleted', variantName: 'ChatInputCompleted', tsInterface: 'ChatInputCompletedAction' }, - { type: 'chat/truncated', variantName: 'ChatTruncated', tsInterface: 'ChatTruncatedAction' }, + { type: 'session/usage', variantName: 'SessionUsage', tsInterface: 'SessionUsageAction' }, + { type: 'session/reasoning', variantName: 'SessionReasoning', tsInterface: 'SessionReasoningAction' }, { type: 'session/modelChanged', variantName: 'SessionModelChanged', tsInterface: 'SessionModelChangedAction' }, { type: 'session/agentChanged', variantName: 'SessionAgentChanged', tsInterface: 'SessionAgentChangedAction' }, { type: 'session/isReadChanged', variantName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, @@ -1241,13 +1131,21 @@ const ACTION_VARIANTS: { { type: 'session/serverToolsChanged', variantName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', variantName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', variantName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, + { type: 'session/pendingMessageSet', variantName: 'SessionPendingMessageSet', tsInterface: 'SessionPendingMessageSetAction' }, + { type: 'session/pendingMessageRemoved', variantName: 'SessionPendingMessageRemoved', tsInterface: 'SessionPendingMessageRemovedAction' }, + { type: 'session/queuedMessagesReordered', variantName: 'SessionQueuedMessagesReordered', tsInterface: 'SessionQueuedMessagesReorderedAction' }, + { type: 'session/inputRequested', variantName: 'SessionInputRequested', tsInterface: 'SessionInputRequestedAction' }, + { type: 'session/inputAnswerChanged', variantName: 'SessionInputAnswerChanged', tsInterface: 'SessionInputAnswerChangedAction' }, + { type: 'session/inputCompleted', variantName: 'SessionInputCompleted', tsInterface: 'SessionInputCompletedAction' }, { type: 'session/customizationsChanged', variantName: 'SessionCustomizationsChanged', tsInterface: 'SessionCustomizationsChangedAction' }, { type: 'session/customizationToggled', variantName: 'SessionCustomizationToggled', tsInterface: 'SessionCustomizationToggledAction' }, { type: 'session/customizationUpdated', variantName: 'SessionCustomizationUpdated', tsInterface: 'SessionCustomizationUpdatedAction' }, { type: 'session/customizationRemoved', variantName: 'SessionCustomizationRemoved', tsInterface: 'SessionCustomizationRemovedAction' }, { type: 'session/mcpServerStateChanged', variantName: 'SessionMcpServerStateChanged', tsInterface: 'SessionMcpServerStateChangedAction' }, + { type: 'session/truncated', variantName: 'SessionTruncated', tsInterface: 'SessionTruncatedAction' }, { type: 'session/configChanged', variantName: 'SessionConfigChanged', tsInterface: 'SessionConfigChangedAction' }, { type: 'session/metaChanged', variantName: 'SessionMetaChanged', tsInterface: 'SessionMetaChangedAction' }, + { type: 'session/toolCallContentChanged', variantName: 'SessionToolCallContentChanged', tsInterface: 'SessionToolCallContentChangedAction' }, { type: 'changeset/statusChanged', variantName: 'ChangesetStatusChanged', tsInterface: 'ChangesetStatusChangedAction' }, { type: 'changeset/fileSet', variantName: 'ChangesetFileSet', tsInterface: 'ChangesetFileSetAction' }, { type: 'changeset/fileRemoved', variantName: 'ChangesetFileRemoved', tsInterface: 'ChangesetFileRemovedAction' }, @@ -1274,10 +1172,10 @@ const ACTION_VARIANTS: { { type: 'resourceWatch/changed', variantName: 'ResourceWatchChanged', tsInterface: 'ResourceWatchChangedAction' }, ]; -function generateMergedChatToolCallConfirmedStruct(): string { - return `// ChatToolCallConfirmedAction is the client approves or denies a +function generateMergedToolCallConfirmedStruct(): string { + return `// SessionToolCallConfirmedAction is the client approves or denies a // pending tool call (merged approved + denied variants on the wire). -type ChatToolCallConfirmedAction struct { +type SessionToolCallConfirmedAction struct { \tType ActionType \`json:"type"\` \tTurnId string \`json:"turnId"\` \tToolCallId string \`json:"toolCallId"\` @@ -1319,8 +1217,8 @@ function generateActionsUnion(): string { variants: ACTION_VARIANTS.map((v) => ({ variantName: v.variantName, innerType: - v.tsInterface === '_chat_tool_call_confirmed_' - ? 'ChatToolCallConfirmedAction' + v.tsInterface === '_merged_' + ? 'SessionToolCallConfirmedAction' : stripIPrefix(v.tsInterface), wireValue: v.type, })), @@ -1344,8 +1242,8 @@ function generateActionsFile(project: Project): string { lines.push('// ─── Action Payloads ─────────────────────────────────────────────────\n'); for (const v of ACTION_VARIANTS) { - if (v.tsInterface === '_chat_tool_call_confirmed_') { - lines.push(generateMergedChatToolCallConfirmedStruct()); + if (v.tsInterface === '_merged_') { + lines.push(generateMergedToolCallConfirmedStruct()); lines.push(''); continue; } @@ -1382,7 +1280,6 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: str { name: 'SubscribeParams' }, { name: 'SubscribeResult' }, { name: 'SessionForkSource' }, { name: 'CreateSessionParams' }, { name: 'DisposeSessionParams' }, - { name: 'ChatForkSource' }, { name: 'CreateChatParams' }, { name: 'DisposeChatParams' }, { name: 'ListSessionsParams' }, { name: 'ListSessionsResult' }, { name: 'ResourceReadParams' }, { name: 'ResourceReadResult' }, { name: 'ResourceWriteParams' }, { name: 'ResourceWriteResult' }, @@ -1845,7 +1742,7 @@ function checkExhaustiveness(project: Project): void { ...COMMAND_ENUMS, ...NOTIFICATION_STRUCTS, ...NOTIFICATION_ENUMS, - ...ACTION_VARIANTS.filter((v) => !v.tsInterface.startsWith('_')).map((v) => v.tsInterface), + ...ACTION_VARIANTS.filter((v) => v.tsInterface !== '_merged_').map((v) => v.tsInterface), ]); const knownSpecial = new Set([ @@ -1861,17 +1758,12 @@ function checkExhaustiveness(project: Project): void { 'SessionToolCallApprovedAction', 'SessionToolCallDeniedAction', 'SessionToolCallConfirmedAction', - 'ChatToolCallApprovedAction', - 'ChatToolCallDeniedAction', - 'ChatToolCallConfirmedAction', - 'ChatAction', 'PingParams', 'TerminalClaim', 'TerminalContentPart', - 'ChatOrigin', - 'ChatInputQuestion', - 'ChatInputAnswerValue', - 'ChatInputAnswer', + 'SessionInputQuestion', + 'SessionInputAnswerValue', + 'SessionInputAnswer', 'MessageAttachment', 'MessageAttachmentBase', 'Customization', diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index c786e3bb..86e29d7a 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -140,13 +140,9 @@ function mapType(tsType: string): string { if ( tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState' || - tsType === 'RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState' || - tsType === 'RootState | SessionState | ChatState' || - tsType === 'RootState | SessionState | ChatState | TerminalState' || - tsType === 'RootState | SessionState | ChatState | TerminalState | ChangesetState' || - tsType === 'RootState | SessionState | ChatState | TerminalState | ChangesetState | AnnotationsState' + tsType === 'RootState | SessionState | TerminalState | ChangesetState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState' + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState' ) { return 'SnapshotState'; } @@ -473,8 +469,8 @@ interface UnionConfig { * and is responsible for its own discriminator field on the wire. * * Multiple discriminant wire values may map to the same `structName` (e.g. - * `ChatInputQuestion` accepts both "number" and "integer" → the same - * `ChatInputNumberQuestion` data class). We deduplicate variants by + * `SessionInputQuestion` accepts both "number" and "integer" → the same + * `SessionInputNumberQuestion` data class). We deduplicate variants by * `structName` for the sealed-interface declaration but preserve every entry * in the deserializer switch. */ @@ -642,14 +638,13 @@ internal object StringOrMarkdownSerializer : KSerializer { function generateSnapshotState(): string { return `/** - * The state payload of a snapshot — root, session, chat, terminal, changeset, + * The state payload of a snapshot — root, session, terminal, changeset, * resource-watch, or annotations state. */ @Serializable(with = SnapshotStateSerializer::class) sealed interface SnapshotState { @JvmInline value class Root(val value: RootState) : SnapshotState @JvmInline value class Session(val value: SessionState) : SnapshotState - @JvmInline value class Chat(val value: ChatState) : SnapshotState @JvmInline value class Terminal(val value: TerminalState) : SnapshotState @JvmInline value class Changeset(val value: ChangesetState) : SnapshotState @JvmInline value class ResourceWatch(val value: ResourceWatchState) : SnapshotState @@ -691,7 +686,6 @@ internal object SnapshotStateSerializer : KSerializer { val element: JsonElement = when (value) { is SnapshotState.Root -> output.json.encodeToJsonElement(RootState.serializer(), value.value) is SnapshotState.Session -> output.json.encodeToJsonElement(SessionState.serializer(), value.value) - is SnapshotState.Chat -> output.json.encodeToJsonElement(ChatState.serializer(), value.value) is SnapshotState.Terminal -> output.json.encodeToJsonElement(TerminalState.serializer(), value.value) is SnapshotState.Changeset -> output.json.encodeToJsonElement(ChangesetState.serializer(), value.value) is SnapshotState.ResourceWatch -> output.json.encodeToJsonElement(ResourceWatchState.serializer(), value.value) @@ -765,8 +759,8 @@ internal object ToolResultContentSerializer : KSerializer { const STATE_ENUMS = [ 'PolicyState', 'PendingMessageKind', 'SessionLifecycle', 'SessionStatus', - 'ChatOriginKind', 'ChatInputAnswerState', 'ChatInputAnswerValueKind', 'ChatInputQuestionKind', - 'ChatInputResponseKind', + 'SessionInputAnswerState', 'SessionInputAnswerValueKind', 'SessionInputQuestionKind', + 'SessionInputResponseKind', 'TurnState', 'MessageKind', 'MessageAttachmentKind', 'ResponsePartKind', 'ToolCallStatus', 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolCallContributorKind', @@ -778,17 +772,17 @@ const STATE_ENUMS = [ const STATE_STRUCTS = [ 'Icon', 'ProtectedResourceMetadata', 'RootState', 'RootConfigState', 'AgentInfo', 'SessionModelInfo', 'ModelSelection', 'AgentSelection', 'ConfigPropertySchema', 'ConfigSchema', - 'PendingMessage', 'ChatState', 'ChatSummary', 'SessionState', 'SessionActiveClient', + 'PendingMessage', 'SessionState', 'SessionActiveClient', 'SessionSummary', 'ChangesSummary', 'ProjectInfo', 'SessionConfigState', 'Turn', 'ActiveTurn', 'Message', - 'ChatInputOption', - 'ChatInputTextAnswerValue', 'ChatInputNumberAnswerValue', - 'ChatInputBooleanAnswerValue', 'ChatInputSelectedAnswerValue', - 'ChatInputSelectedManyAnswerValue', 'ChatInputAnswered', - 'ChatInputSkipped', - 'ChatInputTextQuestion', - 'ChatInputNumberQuestion', 'ChatInputBooleanQuestion', - 'ChatInputSingleSelectQuestion', 'ChatInputMultiSelectQuestion', - 'ChatInputRequest', + 'SessionInputOption', + 'SessionInputTextAnswerValue', 'SessionInputNumberAnswerValue', + 'SessionInputBooleanAnswerValue', 'SessionInputSelectedAnswerValue', + 'SessionInputSelectedManyAnswerValue', 'SessionInputAnswered', + 'SessionInputSkipped', + 'SessionInputTextQuestion', + 'SessionInputNumberQuestion', 'SessionInputBooleanQuestion', + 'SessionInputSingleSelectQuestion', 'SessionInputMultiSelectQuestion', + 'SessionInputRequest', 'TextPosition', 'TextRange', 'TextSelection', 'SimpleMessageAttachment', 'MessageEmbeddedResourceAttachment', 'MessageResourceAttachment', 'MessageAnnotationsAttachment', @@ -868,98 +862,42 @@ const TERMINAL_CONTENT_PART_UNION: UnionConfig = { unknown: true, }; -function generateChatOriginKotlin(): string { - return `@Serializable(with = ChatOriginSerializer::class) -sealed interface ChatOrigin { - @JvmInline value class User(val value: ChatOriginUser) : ChatOrigin - @JvmInline value class Fork(val value: ChatOriginFork) : ChatOrigin - @JvmInline value class Tool(val value: ChatOriginTool) : ChatOrigin - @JvmInline value class Unknown(val raw: JsonObject) : ChatOrigin -} - -@Serializable -data class ChatOriginUser( - val kind: ChatOriginKind = ChatOriginKind.USER, -) - -@Serializable -data class ChatOriginFork( - val kind: ChatOriginKind = ChatOriginKind.FORK, - val chat: String, - val turnId: String, -) - -@Serializable -data class ChatOriginTool( - val kind: ChatOriginKind = ChatOriginKind.TOOL, - val chat: String, - val toolCallId: String, -) - -internal object ChatOriginSerializer : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ChatOrigin") - - override fun deserialize(decoder: Decoder): ChatOrigin { - val input = decoder as? JsonDecoder ?: error("ChatOrigin can only be deserialized from JSON") - val element = input.decodeJsonElement() - val obj = element as? JsonObject ?: error("Expected JsonObject for ChatOrigin") - return when ((obj["kind"] as? JsonPrimitive)?.contentOrNull) { - "user" -> ChatOrigin.User(input.json.decodeFromJsonElement(ChatOriginUser.serializer(), element)) - "fork" -> ChatOrigin.Fork(input.json.decodeFromJsonElement(ChatOriginFork.serializer(), element)) - "tool" -> ChatOrigin.Tool(input.json.decodeFromJsonElement(ChatOriginTool.serializer(), element)) - else -> ChatOrigin.Unknown(obj) - } - } - - override fun serialize(encoder: Encoder, value: ChatOrigin) { - val output = encoder as? JsonEncoder ?: error("ChatOrigin can only be serialized to JSON") - val element: JsonElement = when (value) { - is ChatOrigin.User -> output.json.encodeToJsonElement(ChatOriginUser.serializer(), value.value) - is ChatOrigin.Fork -> output.json.encodeToJsonElement(ChatOriginFork.serializer(), value.value) - is ChatOrigin.Tool -> output.json.encodeToJsonElement(ChatOriginTool.serializer(), value.value) - is ChatOrigin.Unknown -> value.raw - } - output.encodeJsonElement(element) - } -}`; -} - const SESSION_INPUT_QUESTION_UNION: UnionConfig = { - name: 'ChatInputQuestion', + name: 'SessionInputQuestion', discriminantField: 'kind', variants: [ - { caseName: 'Text', structName: 'ChatInputTextQuestion', discriminantValue: 'text' }, + { caseName: 'Text', structName: 'SessionInputTextQuestion', discriminantValue: 'text' }, // Both "number" and "integer" wire values map to the same data class. // Generator deduplicates the sealed-interface variant by struct name. - { caseName: 'Number', structName: 'ChatInputNumberQuestion', discriminantValue: 'number' }, - { caseName: 'Number', structName: 'ChatInputNumberQuestion', discriminantValue: 'integer' }, - { caseName: 'Boolean', structName: 'ChatInputBooleanQuestion', discriminantValue: 'boolean' }, - { caseName: 'SingleSelect', structName: 'ChatInputSingleSelectQuestion', discriminantValue: 'single-select' }, - { caseName: 'MultiSelect', structName: 'ChatInputMultiSelectQuestion', discriminantValue: 'multi-select' }, + { caseName: 'Number', structName: 'SessionInputNumberQuestion', discriminantValue: 'number' }, + { caseName: 'Number', structName: 'SessionInputNumberQuestion', discriminantValue: 'integer' }, + { caseName: 'Boolean', structName: 'SessionInputBooleanQuestion', discriminantValue: 'boolean' }, + { caseName: 'SingleSelect', structName: 'SessionInputSingleSelectQuestion', discriminantValue: 'single-select' }, + { caseName: 'MultiSelect', structName: 'SessionInputMultiSelectQuestion', discriminantValue: 'multi-select' }, ], unknown: true, }; const SESSION_INPUT_ANSWER_VALUE_UNION: UnionConfig = { - name: 'ChatInputAnswerValue', + name: 'SessionInputAnswerValue', discriminantField: 'kind', variants: [ - { caseName: 'Text', structName: 'ChatInputTextAnswerValue', discriminantValue: 'text' }, - { caseName: 'Number', structName: 'ChatInputNumberAnswerValue', discriminantValue: 'number' }, - { caseName: 'Boolean', structName: 'ChatInputBooleanAnswerValue', discriminantValue: 'boolean' }, - { caseName: 'Selected', structName: 'ChatInputSelectedAnswerValue', discriminantValue: 'selected' }, - { caseName: 'SelectedMany', structName: 'ChatInputSelectedManyAnswerValue', discriminantValue: 'selected-many' }, + { caseName: 'Text', structName: 'SessionInputTextAnswerValue', discriminantValue: 'text' }, + { caseName: 'Number', structName: 'SessionInputNumberAnswerValue', discriminantValue: 'number' }, + { caseName: 'Boolean', structName: 'SessionInputBooleanAnswerValue', discriminantValue: 'boolean' }, + { caseName: 'Selected', structName: 'SessionInputSelectedAnswerValue', discriminantValue: 'selected' }, + { caseName: 'SelectedMany', structName: 'SessionInputSelectedManyAnswerValue', discriminantValue: 'selected-many' }, ], unknown: true, }; const SESSION_INPUT_ANSWER_UNION: UnionConfig = { - name: 'ChatInputAnswer', + name: 'SessionInputAnswer', discriminantField: 'state', variants: [ - { caseName: 'Draft', structName: 'ChatInputAnswered', discriminantValue: 'draft' }, - { caseName: 'Submitted', structName: 'ChatInputAnswered', discriminantValue: 'submitted' }, - { caseName: 'Skipped', structName: 'ChatInputSkipped', discriminantValue: 'skipped' }, + { caseName: 'Draft', structName: 'SessionInputAnswered', discriminantValue: 'draft' }, + { caseName: 'Submitted', structName: 'SessionInputAnswered', discriminantValue: 'submitted' }, + { caseName: 'Skipped', structName: 'SessionInputSkipped', discriminantValue: 'skipped' }, ], unknown: true, }; @@ -1073,8 +1011,6 @@ function generateStateFile(project: Project): string { lines.push('// ─── Discriminated Unions ───────────────────────────────────────────────────'); lines.push(''); - lines.push(generateChatOriginKotlin()); - lines.push(''); lines.push(generateDiscriminatedUnion(RESPONSE_PART_UNION)); lines.push(''); lines.push(generateDiscriminatedUnion(TOOL_CALL_STATE_UNION)); @@ -1116,26 +1052,21 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'root/activeSessionsChanged', caseName: 'RootActiveSessionsChanged', tsInterface: 'RootActiveSessionsChangedAction' }, { type: 'session/ready', caseName: 'SessionReady', tsInterface: 'SessionReadyAction' }, { type: 'session/creationFailed', caseName: 'SessionCreationFailed', tsInterface: 'SessionCreationFailedAction' }, - { type: 'session/chatAdded', caseName: 'SessionChatAdded', tsInterface: 'SessionChatAddedAction' }, - { type: 'session/chatRemoved', caseName: 'SessionChatRemoved', tsInterface: 'SessionChatRemovedAction' }, - { type: 'session/chatUpdated', caseName: 'SessionChatUpdated', tsInterface: 'SessionChatUpdatedAction' }, - { type: 'session/defaultChatChanged', caseName: 'SessionDefaultChatChanged', tsInterface: 'SessionDefaultChatChangedAction' }, - { type: 'chat/turnStarted', caseName: 'ChatTurnStarted', tsInterface: 'ChatTurnStartedAction' }, - { type: 'chat/delta', caseName: 'ChatDelta', tsInterface: 'ChatDeltaAction' }, - { type: 'chat/responsePart', caseName: 'ChatResponsePart', tsInterface: 'ChatResponsePartAction' }, - { type: 'chat/toolCallStart', caseName: 'ChatToolCallStart', tsInterface: 'ChatToolCallStartAction' }, - { type: 'chat/toolCallDelta', caseName: 'ChatToolCallDelta', tsInterface: 'ChatToolCallDeltaAction' }, - { type: 'chat/toolCallReady', caseName: 'ChatToolCallReady', tsInterface: 'ChatToolCallReadyAction' }, - { type: 'chat/toolCallConfirmed', caseName: 'ChatToolCallConfirmed', tsInterface: '_merged_chat_' }, - { type: 'chat/toolCallComplete', caseName: 'ChatToolCallComplete', tsInterface: 'ChatToolCallCompleteAction' }, - { type: 'chat/toolCallResultConfirmed', caseName: 'ChatToolCallResultConfirmed', tsInterface: 'ChatToolCallResultConfirmedAction' }, - { type: 'chat/toolCallContentChanged', caseName: 'ChatToolCallContentChanged', tsInterface: 'ChatToolCallContentChangedAction' }, - { type: 'chat/turnComplete', caseName: 'ChatTurnComplete', tsInterface: 'ChatTurnCompleteAction' }, - { type: 'chat/turnCancelled', caseName: 'ChatTurnCancelled', tsInterface: 'ChatTurnCancelledAction' }, - { type: 'chat/error', caseName: 'ChatError', tsInterface: 'ChatErrorAction' }, + { type: 'session/turnStarted', caseName: 'SessionTurnStarted', tsInterface: 'SessionTurnStartedAction' }, + { type: 'session/delta', caseName: 'SessionDelta', tsInterface: 'SessionDeltaAction' }, + { type: 'session/responsePart', caseName: 'SessionResponsePart', tsInterface: 'SessionResponsePartAction' }, + { type: 'session/toolCallStart', caseName: 'SessionToolCallStart', tsInterface: 'SessionToolCallStartAction' }, + { type: 'session/toolCallDelta', caseName: 'SessionToolCallDelta', tsInterface: 'SessionToolCallDeltaAction' }, + { type: 'session/toolCallReady', caseName: 'SessionToolCallReady', tsInterface: 'SessionToolCallReadyAction' }, + { type: 'session/toolCallConfirmed', caseName: 'SessionToolCallConfirmed', tsInterface: '_merged_' }, + { type: 'session/toolCallComplete', caseName: 'SessionToolCallComplete', tsInterface: 'SessionToolCallCompleteAction' }, + { type: 'session/toolCallResultConfirmed', caseName: 'SessionToolCallResultConfirmed', tsInterface: 'SessionToolCallResultConfirmedAction' }, + { type: 'session/turnComplete', caseName: 'SessionTurnComplete', tsInterface: 'SessionTurnCompleteAction' }, + { type: 'session/turnCancelled', caseName: 'SessionTurnCancelled', tsInterface: 'SessionTurnCancelledAction' }, + { type: 'session/error', caseName: 'SessionError', tsInterface: 'SessionErrorAction' }, { type: 'session/titleChanged', caseName: 'SessionTitleChanged', tsInterface: 'SessionTitleChangedAction' }, - { type: 'chat/usage', caseName: 'ChatUsage', tsInterface: 'ChatUsageAction' }, - { type: 'chat/reasoning', caseName: 'ChatReasoning', tsInterface: 'ChatReasoningAction' }, + { type: 'session/usage', caseName: 'SessionUsage', tsInterface: 'SessionUsageAction' }, + { type: 'session/reasoning', caseName: 'SessionReasoning', tsInterface: 'SessionReasoningAction' }, { type: 'session/modelChanged', caseName: 'SessionModelChanged', tsInterface: 'SessionModelChangedAction' }, { type: 'session/agentChanged', caseName: 'SessionAgentChanged', tsInterface: 'SessionAgentChangedAction' }, { type: 'session/isReadChanged', caseName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, @@ -1145,20 +1076,21 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'session/serverToolsChanged', caseName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', caseName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', caseName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, - { type: 'chat/pendingMessageSet', caseName: 'ChatPendingMessageSet', tsInterface: 'ChatPendingMessageSetAction' }, - { type: 'chat/pendingMessageRemoved', caseName: 'ChatPendingMessageRemoved', tsInterface: 'ChatPendingMessageRemovedAction' }, - { type: 'chat/queuedMessagesReordered', caseName: 'ChatQueuedMessagesReordered', tsInterface: 'ChatQueuedMessagesReorderedAction' }, - { type: 'chat/inputRequested', caseName: 'ChatInputRequested', tsInterface: 'ChatInputRequestedAction' }, - { type: 'chat/inputAnswerChanged', caseName: 'ChatInputAnswerChanged', tsInterface: 'ChatInputAnswerChangedAction' }, - { type: 'chat/inputCompleted', caseName: 'ChatInputCompleted', tsInterface: 'ChatInputCompletedAction' }, + { type: 'session/pendingMessageSet', caseName: 'SessionPendingMessageSet', tsInterface: 'SessionPendingMessageSetAction' }, + { type: 'session/pendingMessageRemoved', caseName: 'SessionPendingMessageRemoved', tsInterface: 'SessionPendingMessageRemovedAction' }, + { type: 'session/queuedMessagesReordered', caseName: 'SessionQueuedMessagesReordered', tsInterface: 'SessionQueuedMessagesReorderedAction' }, + { type: 'session/inputRequested', caseName: 'SessionInputRequested', tsInterface: 'SessionInputRequestedAction' }, + { type: 'session/inputAnswerChanged', caseName: 'SessionInputAnswerChanged', tsInterface: 'SessionInputAnswerChangedAction' }, + { type: 'session/inputCompleted', caseName: 'SessionInputCompleted', tsInterface: 'SessionInputCompletedAction' }, { type: 'session/customizationsChanged', caseName: 'SessionCustomizationsChanged', tsInterface: 'SessionCustomizationsChangedAction' }, { type: 'session/customizationToggled', caseName: 'SessionCustomizationToggled', tsInterface: 'SessionCustomizationToggledAction' }, { type: 'session/customizationUpdated', caseName: 'SessionCustomizationUpdated', tsInterface: 'SessionCustomizationUpdatedAction' }, { type: 'session/customizationRemoved', caseName: 'SessionCustomizationRemoved', tsInterface: 'SessionCustomizationRemovedAction' }, { type: 'session/mcpServerStateChanged', caseName: 'SessionMcpServerStateChanged', tsInterface: 'SessionMcpServerStateChangedAction' }, - { type: 'chat/truncated', caseName: 'ChatTruncated', tsInterface: 'ChatTruncatedAction' }, + { type: 'session/truncated', caseName: 'SessionTruncated', tsInterface: 'SessionTruncatedAction' }, { type: 'session/configChanged', caseName: 'SessionConfigChanged', tsInterface: 'SessionConfigChangedAction' }, { type: 'session/metaChanged', caseName: 'SessionMetaChanged', tsInterface: 'SessionMetaChangedAction' }, + { type: 'session/toolCallContentChanged', caseName: 'SessionToolCallContentChanged', tsInterface: 'SessionToolCallContentChangedAction' }, { type: 'changeset/statusChanged', caseName: 'ChangesetStatusChanged', tsInterface: 'ChangesetStatusChangedAction' }, { type: 'changeset/fileSet', caseName: 'ChangesetFileSet', tsInterface: 'ChangesetFileSetAction' }, { type: 'changeset/fileRemoved', caseName: 'ChangesetFileRemoved', tsInterface: 'ChangesetFileRemovedAction' }, @@ -1187,16 +1119,14 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] ]; /** Merged data class for the approved/denied tool call confirmed action. */ -function generateMergedToolCallConfirmedDataClass(scope: 'Session' | 'Chat' = 'Session'): string { - const className = `${scope}ToolCallConfirmedAction`; - const actionType = scope === 'Chat' ? 'ActionType.CHAT_TOOL_CALL_CONFIRMED' : 'ActionType.SESSION_TOOL_CALL_CONFIRMED'; +function generateMergedToolCallConfirmedDataClass(): string { return `/** * Client approves or denies a pending tool call (merged approved + denied variants). */ @Serializable -data class ${className}( +data class SessionToolCallConfirmedAction( /** Action type discriminant */ - val type: ActionType = ${actionType}, + val type: ActionType = ActionType.SESSION_TOOL_CALL_CONFIRMED, /** Turn identifier */ val turnId: String, /** Tool call identifier */ @@ -1243,10 +1173,9 @@ function generateActionsFile(project: Project): string { // Individual action data classes lines.push('// ─── Action Types ───────────────────────────────────────────────────────────'); lines.push(''); - const priorPartialsAction = new Set(requiredPartialStructs); for (const variant of ACTION_VARIANTS) { - if (variant.tsInterface === '_merged_' || variant.tsInterface === '_merged_chat_') { - lines.push(generateMergedToolCallConfirmedDataClass(variant.tsInterface === '_merged_chat_' ? 'Chat' : 'Session')); + if (variant.tsInterface === '_merged_') { + lines.push(generateMergedToolCallConfirmedDataClass()); lines.push(''); continue; } @@ -1259,24 +1188,6 @@ function generateActionsFile(project: Project): string { } } - // Emit any Partial types referenced only by action payloads (e.g. - // Partial on SessionChatUpdatedAction). Mirrors the - // notification-side emission so action-only partials don't slip through. - const actionNewPartials = [...requiredPartialStructs].filter(n => !priorPartialsAction.has(n)); - if (actionNewPartials.length > 0) { - lines.push('// ─── Partial Summary Types ──────────────────────────────────────────────────'); - lines.push(''); - for (const tsName of actionNewPartials) { - try { - lines.push(generatePartialDataClassFromInterface(project, tsName)); - lines.push(''); - } catch (e) { - lines.push(`// TODO: Could not generate Partial<${tsName}>: ${e}`); - lines.push(''); - } - } - } - // StateAction discriminated union lines.push('// ─── StateAction Union ──────────────────────────────────────────────────────'); lines.push(''); @@ -1293,7 +1204,7 @@ function generateActionsFile(project: Project): string { lines.push('sealed interface StateAction'); lines.push(''); for (const v of ACTION_VARIANTS) { - const dataClass = v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : v.tsInterface === '_merged_chat_' ? 'ChatToolCallConfirmedAction' : v.tsInterface; + const dataClass = v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : v.tsInterface; lines.push(`@JvmInline value class StateAction${v.caseName}(val value: ${dataClass}) : StateAction`); } lines.push('@JvmInline value class StateActionUnknown(val raw: JsonObject) : StateAction'); @@ -1313,7 +1224,7 @@ function generateActionsFile(project: Project): string { lines.push(' ?: return StateActionUnknown(obj)'); lines.push(' return when (type) {'); for (const v of ACTION_VARIANTS) { - const dataClass = v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : v.tsInterface === '_merged_chat_' ? 'ChatToolCallConfirmedAction' : v.tsInterface; + const dataClass = v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : v.tsInterface; lines.push(` ${JSON.stringify(v.type)} -> StateAction${v.caseName}(input.json.decodeFromJsonElement(${dataClass}.serializer(), element))`); } lines.push(' else -> StateActionUnknown(obj)'); @@ -1325,7 +1236,7 @@ function generateActionsFile(project: Project): string { lines.push(' ?: error("StateAction can only be serialized to JSON")'); lines.push(' val element: JsonElement = when (value) {'); for (const v of ACTION_VARIANTS) { - const dataClass = v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : v.tsInterface === '_merged_chat_' ? 'ChatToolCallConfirmedAction' : v.tsInterface; + const dataClass = v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : v.tsInterface; lines.push(` is StateAction${v.caseName} -> output.json.encodeToJsonElement(${dataClass}.serializer(), value.value)`); } lines.push(' is StateActionUnknown -> value.raw'); @@ -1348,7 +1259,6 @@ const COMMAND_STRUCTS = [ 'ReconnectParams', 'ReconnectReplayResult', 'ReconnectSnapshotResult', 'SubscribeParams', 'SubscribeResult', 'SessionForkSource', 'CreateSessionParams', 'DisposeSessionParams', - 'ChatForkSource', 'CreateChatParams', 'DisposeChatParams', 'ListSessionsParams', 'ListSessionsResult', 'ResourceReadParams', 'ResourceReadResult', 'ResourceWriteParams', 'ResourceWriteResult', @@ -1844,14 +1754,9 @@ function checkExhaustiveness(project: Project): void { 'SessionToolCallConfirmedAction', // emitted as merged variant 'TerminalClaim', // TERMINAL_CLAIM_UNION discriminated union 'TerminalContentPart', // TERMINAL_CONTENT_PART_UNION discriminated union - 'ChatInputQuestion', // CHAT_INPUT_QUESTION_UNION discriminated union - 'ChatInputAnswerValue', // CHAT_INPUT_ANSWER_VALUE_UNION discriminated union - 'ChatInputAnswer', // CHAT_INPUT_ANSWER_UNION discriminated union - 'ChatOrigin', // hand-generated union for inline variants - 'ChatToolCallApprovedAction', // merged into ChatToolCallConfirmedAction - 'ChatToolCallDeniedAction', // merged into ChatToolCallConfirmedAction - 'ChatToolCallConfirmedAction', // emitted as merged variant - 'ChatAction', // source-only union covered by StateAction + 'SessionInputQuestion', // SESSION_INPUT_QUESTION_UNION discriminated union + 'SessionInputAnswerValue', // SESSION_INPUT_ANSWER_VALUE_UNION discriminated union + 'SessionInputAnswer', // SESSION_INPUT_ANSWER_UNION discriminated union 'MessageAttachment', // MESSAGE_ATTACHMENT_UNION discriminated union 'MessageAttachmentBase', // base interface, flattened into the variant data classes via `extends` 'Customization', // CUSTOMIZATION_UNION discriminated union diff --git a/scripts/generate-markdown.ts b/scripts/generate-markdown.ts index 0e08372f..5e83baef 100644 --- a/scripts/generate-markdown.ts +++ b/scripts/generate-markdown.ts @@ -51,7 +51,6 @@ const DIR_TO_PAGE: Record = { 'common': 'common', 'channels-root': 'root', 'channels-session': 'session', - 'channels-chat': 'chat', 'channels-terminal': 'terminal', 'channels-changeset': 'changeset', 'channels-annotations': 'annotations', @@ -947,35 +946,6 @@ function generateSessionChannelPage(project: Project): string { return lines.join('\n'); } -function generateChatChannelPage(project: Project): string { - currentPage = 'chat'; - const stateSf = findChannelSourceFile(project, 'channels-chat', 'state.ts'); - const actionsSf = findChannelSourceFile(project, 'channels-chat', 'actions.ts'); - const commandsSf = findChannelSourceFile(project, 'channels-chat', 'commands.ts'); - - const lines: string[] = [GENERATED_HEADER]; - lines.push('# Chat Channel\n'); - lines.push('Reference for the `ahp-chat:/` channel — per-chat state, the turn lifecycle, tool-call state machine, attachments, pending messages, and input requests. A chat belongs to a session (see [Session Channel](/reference/session)); a session may contain multiple chats. See [Chat Channel specification](/specification/chat-channel) for the wire-level overview.\n'); - lines.push(schemaLink('state.schema.json')); - - if (stateSf) { - lines.push('## State Types\n'); - lines.push(emitStateTypesSection([stateSf])); - } - if (actionsSf) { - lines.push('## Actions\n'); - lines.push('Mutate `ChatState`. Scoped to a chat URI via the enclosing `ActionEnvelope.channel`.\n'); - lines.push(schemaLink('actions.schema.json')); - lines.push(emitActionsSection([actionsSf])); - } - if (commandsSf) { - lines.push('## Commands\n'); - lines.push(schemaLink('commands.schema.json')); - lines.push(emitCommandsSection(project, [commandsSf])); - } - return lines.join('\n'); -} - function generateTerminalChannelPage(project: Project): string { currentPage = 'terminal'; const stateSf = findChannelSourceFile(project, 'channels-terminal', 'state.ts'); @@ -1301,7 +1271,6 @@ export function generateMarkdownDocs(project: Project, outDir: string): void { { filename: 'common.md', generator: generateCommonPage }, { filename: 'root.md', generator: generateRootChannelPage }, { filename: 'session.md', generator: generateSessionChannelPage }, - { filename: 'chat.md', generator: generateChatChannelPage }, { filename: 'terminal.md', generator: generateTerminalChannelPage }, { filename: 'changeset.md', generator: generateChangesetChannelPage }, { filename: 'annotations.md', generator: generateAnnotationsChannelPage }, diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index eb212f76..ff43ec79 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -152,11 +152,7 @@ function mapType(tsType: string, propName?: string, containerName?: string): str || tsType === 'RootState | SessionState' || tsType === 'RootState | SessionState | TerminalState' || tsType === 'RootState | SessionState | TerminalState | ChangesetState' || tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState' - || tsType === 'RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState' - || tsType === 'RootState | SessionState | ChatState' - || tsType === 'RootState | SessionState | ChatState | TerminalState' - || tsType === 'RootState | SessionState | ChatState | TerminalState | ChangesetState' - || tsType === 'RootState | SessionState | ChatState | TerminalState | ChangesetState | AnnotationsState') { + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState') { return 'SnapshotState'; } @@ -531,8 +527,8 @@ function generateStructFromInterface( const STATE_ENUMS = [ 'PolicyState', 'PendingMessageKind', 'SessionLifecycle', 'SessionStatus', - 'ChatOriginKind', 'ChatInputAnswerState', 'ChatInputAnswerValueKind', 'ChatInputQuestionKind', - 'ChatInputResponseKind', + 'SessionInputAnswerState', 'SessionInputAnswerValueKind', 'SessionInputQuestionKind', + 'SessionInputResponseKind', 'TurnState', 'MessageAttachmentKind', 'ResponsePartKind', 'ToolCallStatus', 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolCallContributorKind', @@ -558,8 +554,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ConfigPropertySchema' }, { name: 'ConfigSchema' }, { name: 'PendingMessage' }, - { name: 'ChatState' }, - { name: 'ChatSummary' }, { name: 'SessionState' }, { name: 'SessionActiveClient' }, { name: 'SessionSummary' }, @@ -571,20 +565,20 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'Turn' }, { name: 'ActiveTurn' }, { name: 'Message' }, - { name: 'ChatInputOption' }, - { name: 'ChatInputTextAnswerValue', omitDiscriminants: true }, - { name: 'ChatInputNumberAnswerValue', omitDiscriminants: true }, - { name: 'ChatInputBooleanAnswerValue', omitDiscriminants: true }, - { name: 'ChatInputSelectedAnswerValue', omitDiscriminants: true }, - { name: 'ChatInputSelectedManyAnswerValue', omitDiscriminants: true }, - { name: 'ChatInputAnswered', omitDiscriminants: true }, - { name: 'ChatInputSkipped', omitDiscriminants: true }, - { name: 'ChatInputTextQuestion', omitDiscriminants: true }, - { name: 'ChatInputNumberQuestion', omitDiscriminants: true }, - { name: 'ChatInputBooleanQuestion', omitDiscriminants: true }, - { name: 'ChatInputSingleSelectQuestion', omitDiscriminants: true }, - { name: 'ChatInputMultiSelectQuestion', omitDiscriminants: true }, - { name: 'ChatInputRequest' }, + { name: 'SessionInputOption' }, + { name: 'SessionInputTextAnswerValue', omitDiscriminants: true }, + { name: 'SessionInputNumberAnswerValue', omitDiscriminants: true }, + { name: 'SessionInputBooleanAnswerValue', omitDiscriminants: true }, + { name: 'SessionInputSelectedAnswerValue', omitDiscriminants: true }, + { name: 'SessionInputSelectedManyAnswerValue', omitDiscriminants: true }, + { name: 'SessionInputAnswered', omitDiscriminants: true }, + { name: 'SessionInputSkipped', omitDiscriminants: true }, + { name: 'SessionInputTextQuestion', omitDiscriminants: true }, + { name: 'SessionInputNumberQuestion', omitDiscriminants: true }, + { name: 'SessionInputBooleanQuestion', omitDiscriminants: true }, + { name: 'SessionInputSingleSelectQuestion', omitDiscriminants: true }, + { name: 'SessionInputMultiSelectQuestion', omitDiscriminants: true }, + { name: 'SessionInputRequest' }, { name: 'TextPosition' }, { name: 'TextRange' }, { name: 'TextSelection' }, @@ -710,43 +704,43 @@ const TERMINAL_CONTENT_PART_UNION: UnionConfig = { unknown: true, }; -const CHAT_INPUT_QUESTION_UNION: UnionConfig = { - name: 'ChatInputQuestion', +const SESSION_INPUT_QUESTION_UNION: UnionConfig = { + name: 'SessionInputQuestion', discriminantField: 'kind', - doc: 'One question within a chat input request.', + doc: 'One question within a session input request.', variants: [ - { variantName: 'Text', innerType: 'ChatInputTextQuestion', wireValue: 'text' }, - { variantName: 'Number', innerType: 'ChatInputNumberQuestion', wireValue: 'number' }, - { variantName: 'Integer', innerType: 'ChatInputNumberQuestion', wireValue: 'integer' }, - { variantName: 'Boolean', innerType: 'ChatInputBooleanQuestion', wireValue: 'boolean' }, - { variantName: 'SingleSelect', innerType: 'ChatInputSingleSelectQuestion', wireValue: 'single-select' }, - { variantName: 'MultiSelect', innerType: 'ChatInputMultiSelectQuestion', wireValue: 'multi-select' }, + { variantName: 'Text', innerType: 'SessionInputTextQuestion', wireValue: 'text' }, + { variantName: 'Number', innerType: 'SessionInputNumberQuestion', wireValue: 'number' }, + { variantName: 'Integer', innerType: 'SessionInputNumberQuestion', wireValue: 'integer' }, + { variantName: 'Boolean', innerType: 'SessionInputBooleanQuestion', wireValue: 'boolean' }, + { variantName: 'SingleSelect', innerType: 'SessionInputSingleSelectQuestion', wireValue: 'single-select' }, + { variantName: 'MultiSelect', innerType: 'SessionInputMultiSelectQuestion', wireValue: 'multi-select' }, ], unknown: true, }; -const CHAT_INPUT_ANSWER_VALUE_UNION: UnionConfig = { - name: 'ChatInputAnswerValue', +const SESSION_INPUT_ANSWER_VALUE_UNION: UnionConfig = { + name: 'SessionInputAnswerValue', discriminantField: 'kind', doc: 'Value captured for one answer.', variants: [ - { variantName: 'Text', innerType: 'ChatInputTextAnswerValue', wireValue: 'text' }, - { variantName: 'Number', innerType: 'ChatInputNumberAnswerValue', wireValue: 'number' }, - { variantName: 'Boolean', innerType: 'ChatInputBooleanAnswerValue', wireValue: 'boolean' }, - { variantName: 'Selected', innerType: 'ChatInputSelectedAnswerValue', wireValue: 'selected' }, - { variantName: 'SelectedMany', innerType: 'ChatInputSelectedManyAnswerValue', wireValue: 'selected-many' }, + { variantName: 'Text', innerType: 'SessionInputTextAnswerValue', wireValue: 'text' }, + { variantName: 'Number', innerType: 'SessionInputNumberAnswerValue', wireValue: 'number' }, + { variantName: 'Boolean', innerType: 'SessionInputBooleanAnswerValue', wireValue: 'boolean' }, + { variantName: 'Selected', innerType: 'SessionInputSelectedAnswerValue', wireValue: 'selected' }, + { variantName: 'SelectedMany', innerType: 'SessionInputSelectedManyAnswerValue', wireValue: 'selected-many' }, ], unknown: true, }; -const CHAT_INPUT_ANSWER_UNION: UnionConfig = { - name: 'ChatInputAnswer', +const SESSION_INPUT_ANSWER_UNION: UnionConfig = { + name: 'SessionInputAnswer', discriminantField: 'state', doc: 'Draft, submitted, or skipped answer for one question.', variants: [ - { variantName: 'Draft', innerType: 'ChatInputAnswered', wireValue: 'draft' }, - { variantName: 'Submitted', innerType: 'ChatInputAnswered', wireValue: 'submitted' }, - { variantName: 'Skipped', innerType: 'ChatInputSkipped', wireValue: 'skipped' }, + { variantName: 'Draft', innerType: 'SessionInputAnswered', wireValue: 'draft' }, + { variantName: 'Submitted', innerType: 'SessionInputAnswered', wireValue: 'submitted' }, + { variantName: 'Skipped', innerType: 'SessionInputSkipped', wireValue: 'skipped' }, ], unknown: true, }; @@ -849,53 +843,18 @@ const TOOL_CALL_CONTRIBUTOR_UNION: UnionConfig = { unknown: true, }; -function generateChatOrigin(): string { - return `/// How a chat came into existence. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "kind")] -pub enum ChatOrigin { - /// Created directly by a user. - #[serde(rename = "user")] - User, - /// Forked from a specific turn of another chat. - #[serde(rename = "fork")] - Fork { - /// URI of the chat this one was forked from. - chat: Uri, - /// Turn the fork was taken from. - #[serde(rename = "turnId")] - turn_id: String, - }, - /// Spawned by a tool call in another chat. - #[serde(rename = "tool")] - Tool { - /// URI of the chat whose tool call spawned this one. - chat: Uri, - /// Tool call that spawned this chat. - #[serde(rename = "toolCallId")] - tool_call_id: String, - }, - /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. - /// Reducers treat this as a no-op. - #[serde(untagged)] - Unknown(serde_json::Value), -}`; -} - function generateSnapshotState(): string { - return `/// The state payload of a snapshot — root, session, chat, terminal, + return `/// The state payload of a snapshot — root, session, terminal, /// changeset, resource-watch, or annotations state. /// /// Deserialized by trying session first (has required \`summary\`), then -/// chat (has required \`turns\`), then terminal (has required \`content\`), -/// then changeset (has required \`status\` and \`files\`), then resource-watch -/// (has required \`root\` and \`recursive\`), then annotations (has required -/// \`annotations\`), then root. +/// terminal (has required \`content\`), then changeset (has required +/// \`status\` and \`files\`), then resource-watch (has required \`root\` and +/// \`recursive\`), then annotations (has required \`annotations\`), then root. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum SnapshotState { Session(Box), - Chat(Box), Terminal(Box), Changeset(Box), ResourceWatch(Box), @@ -930,8 +889,6 @@ function generateStateFile(project: Project): string { } lines.push('// ─── Discriminated Unions ─────────────────────────────────────────────\n'); - lines.push(generateChatOrigin()); - lines.push(''); lines.push(generateDiscriminatedUnion(RESPONSE_PART_UNION)); lines.push(''); lines.push(generateDiscriminatedUnion(TOOL_CALL_STATE_UNION)); @@ -940,11 +897,11 @@ function generateStateFile(project: Project): string { lines.push(''); lines.push(generateDiscriminatedUnion(TERMINAL_CONTENT_PART_UNION)); lines.push(''); - lines.push(generateDiscriminatedUnion(CHAT_INPUT_QUESTION_UNION)); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_QUESTION_UNION)); lines.push(''); - lines.push(generateDiscriminatedUnion(CHAT_INPUT_ANSWER_VALUE_UNION)); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_ANSWER_VALUE_UNION)); lines.push(''); - lines.push(generateDiscriminatedUnion(CHAT_INPUT_ANSWER_UNION)); + lines.push(generateDiscriminatedUnion(SESSION_INPUT_ANSWER_UNION)); lines.push(''); lines.push(generateDiscriminatedUnion(TOOL_RESULT_CONTENT_UNION)); lines.push(''); @@ -981,26 +938,21 @@ const ACTION_VARIANTS: { { type: 'root/configChanged', variantName: 'RootConfigChanged', tsInterface: 'RootConfigChangedAction' }, { type: 'session/ready', variantName: 'SessionReady', tsInterface: 'SessionReadyAction' }, { type: 'session/creationFailed', variantName: 'SessionCreationFailed', tsInterface: 'SessionCreationFailedAction' }, - { type: 'session/chatAdded', variantName: 'SessionChatAdded', tsInterface: 'SessionChatAddedAction' }, - { type: 'session/chatRemoved', variantName: 'SessionChatRemoved', tsInterface: 'SessionChatRemovedAction' }, - { type: 'session/chatUpdated', variantName: 'SessionChatUpdated', tsInterface: 'SessionChatUpdatedAction' }, - { type: 'session/defaultChatChanged', variantName: 'SessionDefaultChatChanged', tsInterface: 'SessionDefaultChatChangedAction' }, - { type: 'chat/turnStarted', variantName: 'ChatTurnStarted', tsInterface: 'ChatTurnStartedAction' }, - { type: 'chat/delta', variantName: 'ChatDelta', tsInterface: 'ChatDeltaAction' }, - { type: 'chat/responsePart', variantName: 'ChatResponsePart', tsInterface: 'ChatResponsePartAction' }, - { type: 'chat/toolCallStart', variantName: 'ChatToolCallStart', tsInterface: 'ChatToolCallStartAction' }, - { type: 'chat/toolCallDelta', variantName: 'ChatToolCallDelta', tsInterface: 'ChatToolCallDeltaAction' }, - { type: 'chat/toolCallReady', variantName: 'ChatToolCallReady', tsInterface: 'ChatToolCallReadyAction' }, - { type: 'chat/toolCallConfirmed', variantName: 'ChatToolCallConfirmed', tsInterface: '_merged_chat_' }, - { type: 'chat/toolCallComplete', variantName: 'ChatToolCallComplete', tsInterface: 'ChatToolCallCompleteAction' }, - { type: 'chat/toolCallResultConfirmed', variantName: 'ChatToolCallResultConfirmed', tsInterface: 'ChatToolCallResultConfirmedAction' }, - { type: 'chat/toolCallContentChanged', variantName: 'ChatToolCallContentChanged', tsInterface: 'ChatToolCallContentChangedAction' }, - { type: 'chat/turnComplete', variantName: 'ChatTurnComplete', tsInterface: 'ChatTurnCompleteAction' }, - { type: 'chat/turnCancelled', variantName: 'ChatTurnCancelled', tsInterface: 'ChatTurnCancelledAction' }, - { type: 'chat/error', variantName: 'ChatError', tsInterface: 'ChatErrorAction' }, + { type: 'session/turnStarted', variantName: 'SessionTurnStarted', tsInterface: 'SessionTurnStartedAction' }, + { type: 'session/delta', variantName: 'SessionDelta', tsInterface: 'SessionDeltaAction' }, + { type: 'session/responsePart', variantName: 'SessionResponsePart', tsInterface: 'SessionResponsePartAction' }, + { type: 'session/toolCallStart', variantName: 'SessionToolCallStart', tsInterface: 'SessionToolCallStartAction' }, + { type: 'session/toolCallDelta', variantName: 'SessionToolCallDelta', tsInterface: 'SessionToolCallDeltaAction' }, + { type: 'session/toolCallReady', variantName: 'SessionToolCallReady', tsInterface: 'SessionToolCallReadyAction' }, + { type: 'session/toolCallConfirmed', variantName: 'SessionToolCallConfirmed', tsInterface: '_merged_' }, + { type: 'session/toolCallComplete', variantName: 'SessionToolCallComplete', tsInterface: 'SessionToolCallCompleteAction' }, + { type: 'session/toolCallResultConfirmed', variantName: 'SessionToolCallResultConfirmed', tsInterface: 'SessionToolCallResultConfirmedAction' }, + { type: 'session/turnComplete', variantName: 'SessionTurnComplete', tsInterface: 'SessionTurnCompleteAction' }, + { type: 'session/turnCancelled', variantName: 'SessionTurnCancelled', tsInterface: 'SessionTurnCancelledAction' }, + { type: 'session/error', variantName: 'SessionError', tsInterface: 'SessionErrorAction' }, { type: 'session/titleChanged', variantName: 'SessionTitleChanged', tsInterface: 'SessionTitleChangedAction' }, - { type: 'chat/usage', variantName: 'ChatUsage', tsInterface: 'ChatUsageAction' }, - { type: 'chat/reasoning', variantName: 'ChatReasoning', tsInterface: 'ChatReasoningAction' }, + { type: 'session/usage', variantName: 'SessionUsage', tsInterface: 'SessionUsageAction' }, + { type: 'session/reasoning', variantName: 'SessionReasoning', tsInterface: 'SessionReasoningAction' }, { type: 'session/modelChanged', variantName: 'SessionModelChanged', tsInterface: 'SessionModelChangedAction' }, { type: 'session/agentChanged', variantName: 'SessionAgentChanged', tsInterface: 'SessionAgentChangedAction' }, { type: 'session/isReadChanged', variantName: 'SessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, @@ -1010,20 +962,21 @@ const ACTION_VARIANTS: { { type: 'session/serverToolsChanged', variantName: 'SessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', variantName: 'SessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', variantName: 'SessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, - { type: 'chat/pendingMessageSet', variantName: 'ChatPendingMessageSet', tsInterface: 'ChatPendingMessageSetAction' }, - { type: 'chat/pendingMessageRemoved', variantName: 'ChatPendingMessageRemoved', tsInterface: 'ChatPendingMessageRemovedAction' }, - { type: 'chat/queuedMessagesReordered', variantName: 'ChatQueuedMessagesReordered', tsInterface: 'ChatQueuedMessagesReorderedAction' }, - { type: 'chat/inputRequested', variantName: 'ChatInputRequested', tsInterface: 'ChatInputRequestedAction' }, - { type: 'chat/inputAnswerChanged', variantName: 'ChatInputAnswerChanged', tsInterface: 'ChatInputAnswerChangedAction' }, - { type: 'chat/inputCompleted', variantName: 'ChatInputCompleted', tsInterface: 'ChatInputCompletedAction' }, + { type: 'session/pendingMessageSet', variantName: 'SessionPendingMessageSet', tsInterface: 'SessionPendingMessageSetAction' }, + { type: 'session/pendingMessageRemoved', variantName: 'SessionPendingMessageRemoved', tsInterface: 'SessionPendingMessageRemovedAction' }, + { type: 'session/queuedMessagesReordered', variantName: 'SessionQueuedMessagesReordered', tsInterface: 'SessionQueuedMessagesReorderedAction' }, + { type: 'session/inputRequested', variantName: 'SessionInputRequested', tsInterface: 'SessionInputRequestedAction' }, + { type: 'session/inputAnswerChanged', variantName: 'SessionInputAnswerChanged', tsInterface: 'SessionInputAnswerChangedAction' }, + { type: 'session/inputCompleted', variantName: 'SessionInputCompleted', tsInterface: 'SessionInputCompletedAction' }, { type: 'session/customizationsChanged', variantName: 'SessionCustomizationsChanged', tsInterface: 'SessionCustomizationsChangedAction' }, { type: 'session/customizationToggled', variantName: 'SessionCustomizationToggled', tsInterface: 'SessionCustomizationToggledAction' }, { type: 'session/customizationUpdated', variantName: 'SessionCustomizationUpdated', tsInterface: 'SessionCustomizationUpdatedAction', boxed: true }, { type: 'session/customizationRemoved', variantName: 'SessionCustomizationRemoved', tsInterface: 'SessionCustomizationRemovedAction' }, { type: 'session/mcpServerStateChanged', variantName: 'SessionMcpServerStateChanged', tsInterface: 'SessionMcpServerStateChangedAction', boxed: true }, - { type: 'chat/truncated', variantName: 'ChatTruncated', tsInterface: 'ChatTruncatedAction' }, + { type: 'session/truncated', variantName: 'SessionTruncated', tsInterface: 'SessionTruncatedAction' }, { type: 'session/configChanged', variantName: 'SessionConfigChanged', tsInterface: 'SessionConfigChangedAction' }, { type: 'session/metaChanged', variantName: 'SessionMetaChanged', tsInterface: 'SessionMetaChangedAction' }, + { type: 'session/toolCallContentChanged', variantName: 'SessionToolCallContentChanged', tsInterface: 'SessionToolCallContentChangedAction' }, { type: 'changeset/statusChanged', variantName: 'ChangesetStatusChanged', tsInterface: 'ChangesetStatusChangedAction' }, { type: 'changeset/fileSet', variantName: 'ChangesetFileSet', tsInterface: 'ChangesetFileSetAction' }, { type: 'changeset/fileRemoved', variantName: 'ChangesetFileRemoved', tsInterface: 'ChangesetFileRemovedAction' }, @@ -1050,11 +1003,11 @@ const ACTION_VARIANTS: { { type: 'resourceWatch/changed', variantName: 'ResourceWatchChanged', tsInterface: 'ResourceWatchChangedAction' }, ]; -function generateMergedToolCallConfirmedStruct(scope: 'Session' | 'Chat' = 'Session'): string { +function generateMergedToolCallConfirmedStruct(): string { return `/// Client approves or denies a pending tool call (merged approved + denied variants). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct ${scope}ToolCallConfirmedAction { +pub struct SessionToolCallConfirmedAction { pub turn_id: String, pub tool_call_id: String, /// Additional provider-specific metadata for this tool call. @@ -1085,7 +1038,7 @@ pub struct ${scope}ToolCallConfirmedAction { function generateActionsFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; - lines.push('use crate::state::{AgentInfo, AgentSelection, Annotation, AnnotationEntry, ChatInputAnswer, ChatInputRequest, ChatInputResponseKind, ChatOrigin, ConfirmationOption, Customization, ErrorInfo, McpServerState, ModelSelection, ResponsePart, SessionActiveClient, TerminalClaim, TerminalInfo, TextRange, ToolCallContributor, ToolCallResult, ToolCallConfirmationReason, ToolCallCancellationReason, ToolDefinition, ToolResultContent, UsageInfo, Message, PendingMessageKind, ChangesetStatus, ChangesetFile, ChangesetOperation, ChangesetOperationStatus, Changeset, ChatSummary};'); + lines.push('use crate::state::{AgentInfo, AgentSelection, Annotation, AnnotationEntry, ConfirmationOption, Customization, ErrorInfo, McpServerState, ModelSelection, ResponsePart, SessionActiveClient, SessionInputAnswer, SessionInputRequest, SessionInputResponseKind, TerminalClaim, TerminalInfo, TextRange, ToolCallContributor, ToolCallResult, ToolCallConfirmationReason, ToolCallCancellationReason, ToolDefinition, ToolResultContent, UsageInfo, Message, PendingMessageKind, ChangesetStatus, ChangesetFile, ChangesetOperation, ChangesetOperationStatus, Changeset};'); lines.push(''); // ActionType enum @@ -1123,10 +1076,9 @@ pub struct ActionEnvelope { // Individual action structs (as variant inner types — omit the `type` field) lines.push('// ─── Action Payloads ─────────────────────────────────────────────────\n'); - const priorPartials = new Set(requiredPartialStructs); for (const v of ACTION_VARIANTS) { - if (v.tsInterface === '_merged_' || v.tsInterface === '_merged_chat_') { - lines.push(generateMergedToolCallConfirmedStruct(v.tsInterface === '_merged_chat_' ? 'Chat' : 'Session')); + if (v.tsInterface === '_merged_') { + lines.push(generateMergedToolCallConfirmedStruct()); lines.push(''); continue; } @@ -1141,31 +1093,11 @@ pub struct ActionEnvelope { } } - // Emit any Partial structs referenced by action payloads (e.g. Partial - // on SessionChatUpdatedAction). Mirrors the notification-side emission. - const newPartials = [...requiredPartialStructs].filter(n => !priorPartials.has(n)); - if (newPartials.length > 0) { - lines.push('// ─── Partial Summaries ────────────────────────────────────────────────\n'); - for (const tsName of newPartials) { - try { - lines.push(generatePartialStruct(project, tsName)); - lines.push(''); - } catch (e) { - lines.push(`// TODO: could not generate Partial<${tsName}>: ${e}`); - lines.push(''); - } - } - } - // StateAction union lines.push('// ─── StateAction Union ───────────────────────────────────────────────\n'); const variants: UnionVariant[] = ACTION_VARIANTS.map(v => ({ variantName: v.variantName, - innerType: v.tsInterface === '_merged_' - ? 'SessionToolCallConfirmedAction' - : v.tsInterface === '_merged_chat_' - ? 'ChatToolCallConfirmedAction' - : stripIPrefix(v.tsInterface), + innerType: v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : stripIPrefix(v.tsInterface), wireValue: v.type, boxed: v.boxed, })); @@ -1194,8 +1126,6 @@ const COMMAND_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: s { name: 'SubscribeParams' }, { name: 'SubscribeResult' }, { name: 'SessionForkSource' }, { name: 'CreateSessionParams' }, { name: 'DisposeSessionParams' }, - { name: 'ChatForkSource' }, { name: 'CreateChatParams' }, - { name: 'DisposeChatParams' }, { name: 'ListSessionsParams' }, { name: 'ListSessionsResult' }, { name: 'ResourceReadParams' }, { name: 'ResourceReadResult' }, { name: 'ResourceWriteParams' }, { name: 'ResourceWriteResult' }, @@ -1235,7 +1165,7 @@ function generateCommandsFile(project: Project): string { lines.push('#[allow(unused_imports)]'); lines.push('use crate::actions::{ActionEnvelope, StateAction};'); lines.push('#[allow(unused_imports)]'); - lines.push('use crate::state::{AgentSelection, ContentRef, Message, MessageAttachment, ModelSelection, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); + lines.push('use crate::state::{AgentSelection, ContentRef, MessageAttachment, ModelSelection, SessionActiveClient, SessionConfigSchema, SessionSummary, Snapshot, SnapshotState, TelemetryCapabilities, TerminalClaim, TextRange, Turn};'); lines.push(''); lines.push('// ─── Enums ────────────────────────────────────────────────────────────\n'); @@ -1594,17 +1524,12 @@ function checkExhaustiveness(project: Project): void { 'SessionToolCallApprovedAction', 'SessionToolCallDeniedAction', 'SessionToolCallConfirmedAction', - 'ChatToolCallApprovedAction', // merged into ChatToolCallConfirmedAction - 'ChatToolCallDeniedAction', // merged into ChatToolCallConfirmedAction - 'ChatToolCallConfirmedAction', // emitted as merged variant - 'ChatAction', // source-only union covered by StateAction - 'ChatOrigin', // hand-generated union for inline variants 'PingParams', 'TerminalClaim', 'TerminalContentPart', - 'ChatInputQuestion', - 'ChatInputAnswerValue', - 'ChatInputAnswer', + 'SessionInputQuestion', + 'SessionInputAnswerValue', + 'SessionInputAnswer', 'MessageAttachment', 'MessageAttachmentBase', 'Customization', // CUSTOMIZATION_UNION discriminated union diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index ed0f6030..96c8bdf2 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -108,11 +108,7 @@ function mapType(tsType: string, propName?: string, containerName?: string): str || tsType === 'RootState | SessionState | TerminalState' || tsType === 'RootState | SessionState | TerminalState | ChangesetState' || tsType === 'RootState | SessionState | TerminalState | ChangesetState | AnnotationsState' - || tsType === 'RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState' - || tsType === 'RootState | SessionState | ChatState' - || tsType === 'RootState | SessionState | ChatState | TerminalState' - || tsType === 'RootState | SessionState | ChatState | TerminalState | ChangesetState' - || tsType === 'RootState | SessionState | ChatState | TerminalState | ChangesetState | AnnotationsState') return 'SnapshotState'; + || tsType === 'RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState') return 'SnapshotState'; // T | null → T? const nullMatch = tsType.match(/^(.+?)\s*\|\s*null$/); @@ -511,8 +507,8 @@ function generatePartialStructFromInterface( const STATE_ENUMS = [ 'PolicyState', 'PendingMessageKind', 'SessionLifecycle', 'SessionStatus', - 'ChatOriginKind', 'ChatInputAnswerState', 'ChatInputAnswerValueKind', 'ChatInputQuestionKind', - 'ChatInputResponseKind', + 'SessionInputAnswerState', 'SessionInputAnswerValueKind', 'SessionInputQuestionKind', + 'SessionInputResponseKind', 'TurnState', 'MessageAttachmentKind', 'ResponsePartKind', 'ToolCallStatus', 'ToolCallConfirmationReason', 'ToolCallCancellationReason', 'ConfirmationOptionKind', 'ToolCallContributorKind', @@ -524,17 +520,17 @@ const STATE_ENUMS = [ const STATE_STRUCTS = [ 'Icon', 'ProtectedResourceMetadata', 'RootState', 'RootConfigState', 'AgentInfo', 'SessionModelInfo', 'ModelSelection', 'AgentSelection', 'ConfigPropertySchema', 'ConfigSchema', - 'PendingMessage', 'ChatState', 'ChatSummary', 'SessionState', 'SessionActiveClient', + 'PendingMessage', 'SessionState', 'SessionActiveClient', 'SessionSummary', 'ChangesSummary', 'ProjectInfo', 'SessionConfigState', 'Turn', 'ActiveTurn', 'Message', - 'ChatInputOption', - 'ChatInputTextAnswerValue', 'ChatInputNumberAnswerValue', - 'ChatInputBooleanAnswerValue', 'ChatInputSelectedAnswerValue', - 'ChatInputSelectedManyAnswerValue', 'ChatInputAnswered', - 'ChatInputSkipped', - 'ChatInputTextQuestion', - 'ChatInputNumberQuestion', 'ChatInputBooleanQuestion', - 'ChatInputSingleSelectQuestion', 'ChatInputMultiSelectQuestion', - 'ChatInputRequest', + 'SessionInputOption', + 'SessionInputTextAnswerValue', 'SessionInputNumberAnswerValue', + 'SessionInputBooleanAnswerValue', 'SessionInputSelectedAnswerValue', + 'SessionInputSelectedManyAnswerValue', 'SessionInputAnswered', + 'SessionInputSkipped', + 'SessionInputTextQuestion', + 'SessionInputNumberQuestion', 'SessionInputBooleanQuestion', + 'SessionInputSingleSelectQuestion', 'SessionInputMultiSelectQuestion', + 'SessionInputRequest', 'TextPosition', 'TextRange', 'TextSelection', 'SimpleMessageAttachment', 'MessageEmbeddedResourceAttachment', 'MessageResourceAttachment', 'MessageAnnotationsAttachment', @@ -611,37 +607,37 @@ const TERMINAL_CONTENT_PART_UNION: UnionConfig = { }; const SESSION_INPUT_QUESTION_UNION: UnionConfig = { - name: 'ChatInputQuestion', + name: 'SessionInputQuestion', discriminantField: 'kind', variants: [ - { caseName: 'text', structName: 'ChatInputTextQuestion', discriminantValue: 'text' }, - { caseName: 'number', structName: 'ChatInputNumberQuestion', discriminantValue: 'number' }, - { caseName: 'integer', structName: 'ChatInputNumberQuestion', discriminantValue: 'integer' }, - { caseName: 'boolean', structName: 'ChatInputBooleanQuestion', discriminantValue: 'boolean' }, - { caseName: 'singleSelect', structName: 'ChatInputSingleSelectQuestion', discriminantValue: 'single-select' }, - { caseName: 'multiSelect', structName: 'ChatInputMultiSelectQuestion', discriminantValue: 'multi-select' }, + { caseName: 'text', structName: 'SessionInputTextQuestion', discriminantValue: 'text' }, + { caseName: 'number', structName: 'SessionInputNumberQuestion', discriminantValue: 'number' }, + { caseName: 'integer', structName: 'SessionInputNumberQuestion', discriminantValue: 'integer' }, + { caseName: 'boolean', structName: 'SessionInputBooleanQuestion', discriminantValue: 'boolean' }, + { caseName: 'singleSelect', structName: 'SessionInputSingleSelectQuestion', discriminantValue: 'single-select' }, + { caseName: 'multiSelect', structName: 'SessionInputMultiSelectQuestion', discriminantValue: 'multi-select' }, ], }; const SESSION_INPUT_ANSWER_VALUE_UNION: UnionConfig = { - name: 'ChatInputAnswerValue', + name: 'SessionInputAnswerValue', discriminantField: 'kind', variants: [ - { caseName: 'text', structName: 'ChatInputTextAnswerValue', discriminantValue: 'text' }, - { caseName: 'number', structName: 'ChatInputNumberAnswerValue', discriminantValue: 'number' }, - { caseName: 'boolean', structName: 'ChatInputBooleanAnswerValue', discriminantValue: 'boolean' }, - { caseName: 'selected', structName: 'ChatInputSelectedAnswerValue', discriminantValue: 'selected' }, - { caseName: 'selectedMany', structName: 'ChatInputSelectedManyAnswerValue', discriminantValue: 'selected-many' }, + { caseName: 'text', structName: 'SessionInputTextAnswerValue', discriminantValue: 'text' }, + { caseName: 'number', structName: 'SessionInputNumberAnswerValue', discriminantValue: 'number' }, + { caseName: 'boolean', structName: 'SessionInputBooleanAnswerValue', discriminantValue: 'boolean' }, + { caseName: 'selected', structName: 'SessionInputSelectedAnswerValue', discriminantValue: 'selected' }, + { caseName: 'selectedMany', structName: 'SessionInputSelectedManyAnswerValue', discriminantValue: 'selected-many' }, ], }; const SESSION_INPUT_ANSWER_UNION: UnionConfig = { - name: 'ChatInputAnswer', + name: 'SessionInputAnswer', discriminantField: 'state', variants: [ - { caseName: 'draft', structName: 'ChatInputAnswered', discriminantValue: 'draft' }, - { caseName: 'submitted', structName: 'ChatInputAnswered', discriminantValue: 'submitted' }, - { caseName: 'skipped', structName: 'ChatInputSkipped', discriminantValue: 'skipped' }, + { caseName: 'draft', structName: 'SessionInputAnswered', discriminantValue: 'draft' }, + { caseName: 'submitted', structName: 'SessionInputAnswered', discriminantValue: 'submitted' }, + { caseName: 'skipped', structName: 'SessionInputSkipped', discriminantValue: 'skipped' }, ], }; @@ -800,11 +796,10 @@ public enum StringOrMarkdown: Codable, Sendable, Equatable { } function generateSnapshotState(): string { - return `/// The state payload of a snapshot — root, session, chat, terminal, changeset, resource-watch, or annotations state. + return `/// The state payload of a snapshot — root, session, terminal, changeset, resource-watch, or annotations state. public enum SnapshotState: Codable, Sendable { case root(RootState) case session(SessionState) - case chat(ChatState) case terminal(TerminalState) case changeset(ChangesetState) case resourceWatch(ResourceWatchState) @@ -831,7 +826,6 @@ public enum SnapshotState: Codable, Sendable { switch self { case .root(let state): try state.encode(to: encoder) case .session(let state): try state.encode(to: encoder) - case .chat(let state): try state.encode(to: encoder) case .terminal(let state): try state.encode(to: encoder) case .changeset(let state): try state.encode(to: encoder) case .resourceWatch(let state): try state.encode(to: encoder) @@ -841,69 +835,6 @@ public enum SnapshotState: Codable, Sendable { }`; } - -function generateChatOriginSwift(): string { - return `public struct ChatOriginUser: Codable, Sendable { - public var kind: ChatOriginKind - - public init(kind: ChatOriginKind = .user) { - self.kind = kind - } -} - -public struct ChatOriginFork: Codable, Sendable { - public var kind: ChatOriginKind - public var chat: String - public var turnId: String - - public init(kind: ChatOriginKind = .fork, chat: String, turnId: String) { - self.kind = kind - self.chat = chat - self.turnId = turnId - } -} - -public struct ChatOriginTool: Codable, Sendable { - public var kind: ChatOriginKind - public var chat: String - public var toolCallId: String - - public init(kind: ChatOriginKind = .tool, chat: String, toolCallId: String) { - self.kind = kind - self.chat = chat - self.toolCallId = toolCallId - } -} - -public enum ChatOrigin: Codable, Sendable { - case user(ChatOriginUser) - case fork(ChatOriginFork) - case tool(ChatOriginTool) - - private enum DiscriminatorCodingKeys: String, CodingKey { case kind } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: DiscriminatorCodingKeys.self) - let discriminant = try container.decode(String.self, forKey: .kind) - switch discriminant { - case "user": self = .user(try ChatOriginUser(from: decoder)) - case "fork": self = .fork(try ChatOriginFork(from: decoder)) - case "tool": self = .tool(try ChatOriginTool(from: decoder)) - default: - throw DecodingError.dataCorruptedError(forKey: .kind, in: container, debugDescription: "Unknown ChatOrigin kind: \\(discriminant)") - } - } - - public func encode(to encoder: Encoder) throws { - switch self { - case .user(let value): try value.encode(to: encoder) - case .fork(let value): try value.encode(to: encoder) - case .tool(let value): try value.encode(to: encoder) - } - } -}`; -} - function generateStateFile(project: Project): string { const lines: string[] = [GENERATED_HEADER]; @@ -935,8 +866,6 @@ function generateStateFile(project: Project): string { } lines.push('// MARK: - Discriminated Unions\n'); - lines.push(generateChatOriginSwift()); - lines.push(''); lines.push(generateDiscriminatedUnion(RESPONSE_PART_UNION)); lines.push(''); lines.push(generateDiscriminatedUnion(TOOL_CALL_STATE_UNION)); @@ -979,26 +908,21 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'root/activeSessionsChanged', caseName: 'rootActiveSessionsChanged', tsInterface: 'RootActiveSessionsChangedAction' }, { type: 'session/ready', caseName: 'sessionReady', tsInterface: 'SessionReadyAction' }, { type: 'session/creationFailed', caseName: 'sessionCreationFailed', tsInterface: 'SessionCreationFailedAction' }, - { type: 'session/chatAdded', caseName: 'sessionChatAdded', tsInterface: 'SessionChatAddedAction' }, - { type: 'session/chatRemoved', caseName: 'sessionChatRemoved', tsInterface: 'SessionChatRemovedAction' }, - { type: 'session/chatUpdated', caseName: 'sessionChatUpdated', tsInterface: 'SessionChatUpdatedAction' }, - { type: 'session/defaultChatChanged', caseName: 'sessionDefaultChatChanged', tsInterface: 'SessionDefaultChatChangedAction' }, - { type: 'chat/turnStarted', caseName: 'chatTurnStarted', tsInterface: 'ChatTurnStartedAction' }, - { type: 'chat/delta', caseName: 'chatDelta', tsInterface: 'ChatDeltaAction' }, - { type: 'chat/responsePart', caseName: 'chatResponsePart', tsInterface: 'ChatResponsePartAction' }, - { type: 'chat/toolCallStart', caseName: 'chatToolCallStart', tsInterface: 'ChatToolCallStartAction' }, - { type: 'chat/toolCallDelta', caseName: 'chatToolCallDelta', tsInterface: 'ChatToolCallDeltaAction' }, - { type: 'chat/toolCallReady', caseName: 'chatToolCallReady', tsInterface: 'ChatToolCallReadyAction' }, - { type: 'chat/toolCallConfirmed', caseName: 'chatToolCallConfirmed', tsInterface: '_merged_chat_' }, - { type: 'chat/toolCallComplete', caseName: 'chatToolCallComplete', tsInterface: 'ChatToolCallCompleteAction' }, - { type: 'chat/toolCallResultConfirmed', caseName: 'chatToolCallResultConfirmed', tsInterface: 'ChatToolCallResultConfirmedAction' }, - { type: 'chat/toolCallContentChanged', caseName: 'chatToolCallContentChanged', tsInterface: 'ChatToolCallContentChangedAction' }, - { type: 'chat/turnComplete', caseName: 'chatTurnComplete', tsInterface: 'ChatTurnCompleteAction' }, - { type: 'chat/turnCancelled', caseName: 'chatTurnCancelled', tsInterface: 'ChatTurnCancelledAction' }, - { type: 'chat/error', caseName: 'chatError', tsInterface: 'ChatErrorAction' }, + { type: 'session/turnStarted', caseName: 'sessionTurnStarted', tsInterface: 'SessionTurnStartedAction' }, + { type: 'session/delta', caseName: 'sessionDelta', tsInterface: 'SessionDeltaAction' }, + { type: 'session/responsePart', caseName: 'sessionResponsePart', tsInterface: 'SessionResponsePartAction' }, + { type: 'session/toolCallStart', caseName: 'sessionToolCallStart', tsInterface: 'SessionToolCallStartAction' }, + { type: 'session/toolCallDelta', caseName: 'sessionToolCallDelta', tsInterface: 'SessionToolCallDeltaAction' }, + { type: 'session/toolCallReady', caseName: 'sessionToolCallReady', tsInterface: 'SessionToolCallReadyAction' }, + { type: 'session/toolCallConfirmed', caseName: 'sessionToolCallConfirmed', tsInterface: '_merged_' }, + { type: 'session/toolCallComplete', caseName: 'sessionToolCallComplete', tsInterface: 'SessionToolCallCompleteAction' }, + { type: 'session/toolCallResultConfirmed', caseName: 'sessionToolCallResultConfirmed', tsInterface: 'SessionToolCallResultConfirmedAction' }, + { type: 'session/turnComplete', caseName: 'sessionTurnComplete', tsInterface: 'SessionTurnCompleteAction' }, + { type: 'session/turnCancelled', caseName: 'sessionTurnCancelled', tsInterface: 'SessionTurnCancelledAction' }, + { type: 'session/error', caseName: 'sessionError', tsInterface: 'SessionErrorAction' }, { type: 'session/titleChanged', caseName: 'sessionTitleChanged', tsInterface: 'SessionTitleChangedAction' }, - { type: 'chat/usage', caseName: 'chatUsage', tsInterface: 'ChatUsageAction' }, - { type: 'chat/reasoning', caseName: 'chatReasoning', tsInterface: 'ChatReasoningAction' }, + { type: 'session/usage', caseName: 'sessionUsage', tsInterface: 'SessionUsageAction' }, + { type: 'session/reasoning', caseName: 'sessionReasoning', tsInterface: 'SessionReasoningAction' }, { type: 'session/modelChanged', caseName: 'sessionModelChanged', tsInterface: 'SessionModelChangedAction' }, { type: 'session/agentChanged', caseName: 'sessionAgentChanged', tsInterface: 'SessionAgentChangedAction' }, { type: 'session/isReadChanged', caseName: 'sessionIsReadChanged', tsInterface: 'SessionIsReadChangedAction' }, @@ -1008,20 +932,21 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] { type: 'session/serverToolsChanged', caseName: 'sessionServerToolsChanged', tsInterface: 'SessionServerToolsChangedAction' }, { type: 'session/activeClientChanged', caseName: 'sessionActiveClientChanged', tsInterface: 'SessionActiveClientChangedAction' }, { type: 'session/activeClientToolsChanged', caseName: 'sessionActiveClientToolsChanged', tsInterface: 'SessionActiveClientToolsChangedAction' }, - { type: 'chat/pendingMessageSet', caseName: 'chatPendingMessageSet', tsInterface: 'ChatPendingMessageSetAction' }, - { type: 'chat/pendingMessageRemoved', caseName: 'chatPendingMessageRemoved', tsInterface: 'ChatPendingMessageRemovedAction' }, - { type: 'chat/queuedMessagesReordered', caseName: 'chatQueuedMessagesReordered', tsInterface: 'ChatQueuedMessagesReorderedAction' }, - { type: 'chat/inputRequested', caseName: 'chatInputRequested', tsInterface: 'ChatInputRequestedAction' }, - { type: 'chat/inputAnswerChanged', caseName: 'chatInputAnswerChanged', tsInterface: 'ChatInputAnswerChangedAction' }, - { type: 'chat/inputCompleted', caseName: 'chatInputCompleted', tsInterface: 'ChatInputCompletedAction' }, + { type: 'session/pendingMessageSet', caseName: 'sessionPendingMessageSet', tsInterface: 'SessionPendingMessageSetAction' }, + { type: 'session/pendingMessageRemoved', caseName: 'sessionPendingMessageRemoved', tsInterface: 'SessionPendingMessageRemovedAction' }, + { type: 'session/queuedMessagesReordered', caseName: 'sessionQueuedMessagesReordered', tsInterface: 'SessionQueuedMessagesReorderedAction' }, + { type: 'session/inputRequested', caseName: 'sessionInputRequested', tsInterface: 'SessionInputRequestedAction' }, + { type: 'session/inputAnswerChanged', caseName: 'sessionInputAnswerChanged', tsInterface: 'SessionInputAnswerChangedAction' }, + { type: 'session/inputCompleted', caseName: 'sessionInputCompleted', tsInterface: 'SessionInputCompletedAction' }, { type: 'session/customizationsChanged', caseName: 'sessionCustomizationsChanged', tsInterface: 'SessionCustomizationsChangedAction' }, { type: 'session/customizationToggled', caseName: 'sessionCustomizationToggled', tsInterface: 'SessionCustomizationToggledAction' }, { type: 'session/customizationUpdated', caseName: 'sessionCustomizationUpdated', tsInterface: 'SessionCustomizationUpdatedAction' }, { type: 'session/customizationRemoved', caseName: 'sessionCustomizationRemoved', tsInterface: 'SessionCustomizationRemovedAction' }, - { type: 'session/mcpServerStateChanged', caseName: 'sessionMcpServerStateChanged', tsInterface: 'SessionMcpServerStateChangedAction' }, - { type: 'chat/truncated', caseName: 'chatTruncated', tsInterface: 'ChatTruncatedAction' }, + { type: 'session/mcpServerStateChanged', caseName: 'sessionMcpServerStatusChanged', tsInterface: 'SessionMcpServerStateChangedAction' }, + { type: 'session/truncated', caseName: 'sessionTruncated', tsInterface: 'SessionTruncatedAction' }, { type: 'session/configChanged', caseName: 'sessionConfigChanged', tsInterface: 'SessionConfigChangedAction' }, { type: 'session/metaChanged', caseName: 'sessionMetaChanged', tsInterface: 'SessionMetaChangedAction' }, + { type: 'session/toolCallContentChanged', caseName: 'sessionToolCallContentChanged', tsInterface: 'SessionToolCallContentChangedAction' }, { type: 'changeset/statusChanged', caseName: 'changesetStatusChanged', tsInterface: 'ChangesetStatusChangedAction' }, { type: 'changeset/fileSet', caseName: 'changesetFileSet', tsInterface: 'ChangesetFileSetAction' }, { type: 'changeset/fileRemoved', caseName: 'changesetFileRemoved', tsInterface: 'ChangesetFileRemovedAction' }, @@ -1050,11 +975,9 @@ const ACTION_VARIANTS: { type: string; caseName: string; tsInterface: string }[] ]; /** Merged struct for the approved/denied tool call confirmed action */ -function generateMergedToolCallConfirmedStruct(scope: 'Session' | 'Chat' = 'Session'): string { - const className = `${scope}ToolCallConfirmedAction`; - const wireType = scope === 'Chat' ? 'chat/toolCallConfirmed' : 'session/toolCallConfirmed'; +function generateMergedToolCallConfirmedStruct(): string { return `/// Client approves or denies a pending tool call (merged approved + denied variants). -public struct ${className}: Codable, Sendable { +public struct SessionToolCallConfirmedAction: Codable, Sendable { /// Action type discriminant public var type: String /// Turn identifier @@ -1084,7 +1007,7 @@ public struct ${className}: Codable, Sendable { } public init( - type: String = "${wireType}", + type: String = "session/toolCallConfirmed", turnId: String, toolCallId: String, approved: Bool, @@ -1131,10 +1054,9 @@ function generateActionsFile(project: Project): string { // Individual action structs lines.push('// MARK: - Action Types\n'); - const priorPartialsAction = new Set(requiredPartialStructs); for (const variant of ACTION_VARIANTS) { - if (variant.tsInterface === '_merged_' || variant.tsInterface === '_merged_chat_') { - lines.push(generateMergedToolCallConfirmedStruct(variant.tsInterface === '_merged_chat_' ? 'Chat' : 'Session')); + if (variant.tsInterface === '_merged_') { + lines.push(generateMergedToolCallConfirmedStruct()); lines.push(''); continue; } @@ -1147,29 +1069,12 @@ function generateActionsFile(project: Project): string { } } - // Emit any Partial types referenced only by action payloads (e.g. - // Partial on SessionChatUpdatedAction). Mirrors the - // notification-side emission. - const actionNewPartials = [...requiredPartialStructs].filter(n => !priorPartialsAction.has(n)); - if (actionNewPartials.length > 0) { - lines.push('// MARK: - Partial Summary Types\n'); - for (const tsName of actionNewPartials) { - try { - lines.push(generatePartialStructFromInterface(project, tsName)); - lines.push(''); - } catch (e) { - lines.push(`// TODO: Could not generate Partial<${tsName}>: ${e}`); - lines.push(''); - } - } - } - // StateAction discriminated union lines.push('// MARK: - StateAction Union\n'); lines.push('/// Discriminated union of all state actions.'); lines.push('public enum StateAction: Codable, Sendable {'); for (const v of ACTION_VARIANTS) { - lines.push(` case ${v.caseName}(${v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : v.tsInterface === '_merged_chat_' ? 'ChatToolCallConfirmedAction' : v.tsInterface})`); + lines.push(` case ${v.caseName}(${v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' : v.tsInterface})`); } lines.push(' /// Unknown or future action type; reducers treat this as a no-op.'); lines.push(' case unknown(type: String)'); @@ -1183,9 +1088,7 @@ function generateActionsFile(project: Project): string { for (const v of ACTION_VARIANTS) { const structName = v.tsInterface === '_merged_' ? 'SessionToolCallConfirmedAction' - : v.tsInterface === '_merged_chat_' - ? 'ChatToolCallConfirmedAction' - : v.tsInterface; + : v.tsInterface; lines.push(` case ${JSON.stringify(v.type)}:`); lines.push(` self = .${v.caseName}(try ${structName}(from: decoder))`); } @@ -1217,7 +1120,6 @@ const COMMAND_STRUCTS = [ 'ReconnectParams', 'ReconnectReplayResult', 'ReconnectSnapshotResult', 'SubscribeParams', 'SubscribeResult', 'SessionForkSource', 'CreateSessionParams', 'DisposeSessionParams', - 'ChatForkSource', 'CreateChatParams', 'DisposeChatParams', 'ListSessionsParams', 'ListSessionsResult', 'ResourceReadParams', 'ResourceReadResult', 'ResourceWriteParams', 'ResourceWriteResult', @@ -1757,14 +1659,9 @@ function checkExhaustiveness(project: Project): void { 'PingParams', // empty interface; no Swift type emitted 'TerminalClaim', // TERMINAL_CLAIM_UNION discriminated union 'TerminalContentPart', // TERMINAL_CONTENT_PART_UNION discriminated union - 'ChatInputQuestion', // SESSION_INPUT_QUESTION_UNION discriminated union - 'ChatInputAnswerValue', // SESSION_INPUT_ANSWER_VALUE_UNION discriminated union - 'ChatInputAnswer', // CHAT_INPUT_ANSWER_UNION discriminated union - 'ChatOrigin', // hand-generated union for inline variants - 'ChatToolCallApprovedAction', - 'ChatToolCallDeniedAction', - 'ChatToolCallConfirmedAction', - 'ChatAction', + 'SessionInputQuestion', // SESSION_INPUT_QUESTION_UNION discriminated union + 'SessionInputAnswerValue', // SESSION_INPUT_ANSWER_VALUE_UNION discriminated union + 'SessionInputAnswer', // SESSION_INPUT_ANSWER_UNION discriminated union 'MessageAttachment', // MESSAGE_ATTACHMENT_UNION discriminated union 'MessageAttachmentBase', // base interface, flattened into the variant structs via `extends` 'Customization', // CUSTOMIZATION_UNION discriminated union diff --git a/types/action-origin.generated.ts b/types/action-origin.generated.ts index d9c476ca..923d5f9b 100644 --- a/types/action-origin.generated.ts +++ b/types/action-origin.generated.ts @@ -9,49 +9,45 @@ import type { RootConfigChangedAction, SessionReadyAction, SessionCreationFailedAction, - SessionChatAddedAction, - SessionChatRemovedAction, - SessionChatUpdatedAction, - SessionDefaultChatChangedAction, + SessionTurnStartedAction, + SessionDeltaAction, + SessionResponsePartAction, + SessionToolCallStartAction, + SessionToolCallDeltaAction, + SessionToolCallReadyAction, + SessionToolCallConfirmedAction, + SessionToolCallCompleteAction, + SessionToolCallResultConfirmedAction, + SessionToolCallContentChangedAction, + SessionTurnCompleteAction, + SessionTurnCancelledAction, + SessionErrorAction, SessionTitleChangedAction, + SessionUsageAction, + SessionReasoningAction, SessionModelChangedAction, SessionAgentChangedAction, SessionServerToolsChangedAction, SessionActiveClientChangedAction, SessionActiveClientToolsChangedAction, + SessionPendingMessageSetAction, + SessionPendingMessageRemovedAction, + SessionQueuedMessagesReorderedAction, + SessionInputRequestedAction, + SessionInputAnswerChangedAction, + SessionInputCompletedAction, SessionCustomizationsChangedAction, SessionCustomizationToggledAction, SessionCustomizationUpdatedAction, SessionCustomizationRemovedAction, SessionMcpServerStateChangedAction, + SessionTruncatedAction, SessionIsReadChangedAction, SessionIsArchivedChangedAction, SessionActivityChangedAction, SessionChangesetsChangedAction, SessionConfigChangedAction, SessionMetaChangedAction, - ChatTurnStartedAction, - ChatDeltaAction, - ChatResponsePartAction, - ChatToolCallStartAction, - ChatToolCallDeltaAction, - ChatToolCallReadyAction, - ChatToolCallConfirmedAction, - ChatToolCallCompleteAction, - ChatToolCallResultConfirmedAction, - ChatToolCallContentChangedAction, - ChatTurnCompleteAction, - ChatTurnCancelledAction, - ChatErrorAction, - ChatUsageAction, - ChatReasoningAction, - ChatPendingMessageSetAction, - ChatPendingMessageRemovedAction, - ChatQueuedMessagesReorderedAction, - ChatInputRequestedAction, - ChatInputAnswerChangedAction, - ChatInputCompletedAction, - ChatTruncatedAction, ChangesetStatusChangedAction, ChangesetFileSetAction, ChangesetFileRemovedAction, @@ -79,7 +75,7 @@ import type { import { ActionType } from './actions.js'; -// ─── Root vs Session vs Chat vs Terminal vs Changeset Action Unions ───────────────── +// ─── Root vs Session vs Terminal vs Changeset Action Unions ───────────────── /** Union of all root-scoped actions. */ export type RootAction = @@ -105,21 +101,39 @@ export type ServerRootAction = export type SessionAction = | SessionReadyAction | SessionCreationFailedAction - | SessionChatAddedAction - | SessionChatRemovedAction - | SessionChatUpdatedAction - | SessionDefaultChatChangedAction + | SessionTurnStartedAction + | SessionDeltaAction + | SessionResponsePartAction + | SessionToolCallStartAction + | SessionToolCallDeltaAction + | SessionToolCallReadyAction + | SessionToolCallConfirmedAction + | SessionToolCallCompleteAction + | SessionToolCallResultConfirmedAction + | SessionToolCallContentChangedAction + | SessionTurnCompleteAction + | SessionTurnCancelledAction + | SessionErrorAction | SessionTitleChangedAction + | SessionUsageAction + | SessionReasoningAction | SessionModelChangedAction | SessionAgentChangedAction | SessionServerToolsChangedAction | SessionActiveClientChangedAction | SessionActiveClientToolsChangedAction + | SessionPendingMessageSetAction + | SessionPendingMessageRemovedAction + | SessionQueuedMessagesReorderedAction + | SessionInputRequestedAction + | SessionInputAnswerChangedAction + | SessionInputCompletedAction | SessionCustomizationsChangedAction | SessionCustomizationToggledAction | SessionCustomizationUpdatedAction | SessionCustomizationRemovedAction | SessionMcpServerStateChangedAction + | SessionTruncatedAction | SessionIsReadChangedAction | SessionIsArchivedChangedAction | SessionActivityChangedAction @@ -130,12 +144,24 @@ export type SessionAction = /** Union of session actions that clients may dispatch. */ export type ClientSessionAction = + | SessionTurnStartedAction + | SessionToolCallConfirmedAction + | SessionToolCallCompleteAction + | SessionToolCallResultConfirmedAction + | SessionToolCallContentChangedAction + | SessionTurnCancelledAction | SessionTitleChangedAction | SessionModelChangedAction | SessionAgentChangedAction | SessionActiveClientChangedAction | SessionActiveClientToolsChangedAction + | SessionPendingMessageSetAction + | SessionPendingMessageRemovedAction + | SessionQueuedMessagesReorderedAction + | SessionInputAnswerChangedAction + | SessionInputCompletedAction | SessionCustomizationToggledAction + | SessionTruncatedAction | SessionIsReadChangedAction | SessionIsArchivedChangedAction | SessionConfigChangedAction @@ -145,11 +171,17 @@ export type ClientSessionAction = export type ServerSessionAction = | SessionReadyAction | SessionCreationFailedAction - | SessionChatAddedAction - | SessionChatRemovedAction - | SessionChatUpdatedAction - | SessionDefaultChatChangedAction + | SessionDeltaAction + | SessionResponsePartAction + | SessionToolCallStartAction + | SessionToolCallDeltaAction + | SessionToolCallReadyAction + | SessionTurnCompleteAction + | SessionErrorAction + | SessionUsageAction + | SessionReasoningAction | SessionServerToolsChangedAction + | SessionInputRequestedAction | SessionCustomizationsChangedAction | SessionCustomizationUpdatedAction | SessionCustomizationRemovedAction @@ -159,62 +191,6 @@ export type ServerSessionAction = | SessionMetaChangedAction ; -/** Union of all chat-scoped actions. */ -export type ChatAction = - | ChatTurnStartedAction - | ChatDeltaAction - | ChatResponsePartAction - | ChatToolCallStartAction - | ChatToolCallDeltaAction - | ChatToolCallReadyAction - | ChatToolCallConfirmedAction - | ChatToolCallCompleteAction - | ChatToolCallResultConfirmedAction - | ChatToolCallContentChangedAction - | ChatTurnCompleteAction - | ChatTurnCancelledAction - | ChatErrorAction - | ChatUsageAction - | ChatReasoningAction - | ChatPendingMessageSetAction - | ChatPendingMessageRemovedAction - | ChatQueuedMessagesReorderedAction - | ChatInputRequestedAction - | ChatInputAnswerChangedAction - | ChatInputCompletedAction - | ChatTruncatedAction -; - -/** Union of chat actions that clients may dispatch. */ -export type ClientChatAction = - | ChatTurnStartedAction - | ChatToolCallConfirmedAction - | ChatToolCallCompleteAction - | ChatToolCallResultConfirmedAction - | ChatToolCallContentChangedAction - | ChatTurnCancelledAction - | ChatPendingMessageSetAction - | ChatPendingMessageRemovedAction - | ChatQueuedMessagesReorderedAction - | ChatInputAnswerChangedAction - | ChatInputCompletedAction - | ChatTruncatedAction -; - -/** Union of chat actions that only the server may produce. */ -export type ServerChatAction = - | ChatDeltaAction - | ChatResponsePartAction - | ChatToolCallStartAction - | ChatToolCallDeltaAction - | ChatToolCallReadyAction - | ChatTurnCompleteAction - | ChatErrorAction - | ChatUsageAction - | ChatReasoningAction - | ChatInputRequestedAction -; - /** Union of all terminal-scoped actions. */ export type TerminalAction = | TerminalDataAction @@ -325,49 +301,45 @@ export const IS_CLIENT_DISPATCHABLE: { readonly [K in StateAction['type']]: bool [ActionType.RootConfigChanged]: true, [ActionType.SessionReady]: false, [ActionType.SessionCreationFailed]: false, - [ActionType.SessionChatAdded]: false, - [ActionType.SessionChatRemoved]: false, - [ActionType.SessionChatUpdated]: false, - [ActionType.SessionDefaultChatChanged]: false, + [ActionType.SessionTurnStarted]: true, + [ActionType.SessionDelta]: false, + [ActionType.SessionResponsePart]: false, + [ActionType.SessionToolCallStart]: false, + [ActionType.SessionToolCallDelta]: false, + [ActionType.SessionToolCallReady]: false, + [ActionType.SessionToolCallConfirmed]: true, + [ActionType.SessionToolCallComplete]: true, + [ActionType.SessionToolCallResultConfirmed]: true, + [ActionType.SessionToolCallContentChanged]: true, + [ActionType.SessionTurnComplete]: false, + [ActionType.SessionTurnCancelled]: true, + [ActionType.SessionError]: false, [ActionType.SessionTitleChanged]: true, + [ActionType.SessionUsage]: false, + [ActionType.SessionReasoning]: false, [ActionType.SessionModelChanged]: true, [ActionType.SessionAgentChanged]: true, [ActionType.SessionServerToolsChanged]: false, [ActionType.SessionActiveClientChanged]: true, [ActionType.SessionActiveClientToolsChanged]: true, + [ActionType.SessionPendingMessageSet]: true, + [ActionType.SessionPendingMessageRemoved]: true, + [ActionType.SessionQueuedMessagesReordered]: true, + [ActionType.SessionInputRequested]: false, + [ActionType.SessionInputAnswerChanged]: true, + [ActionType.SessionInputCompleted]: true, [ActionType.SessionCustomizationsChanged]: false, [ActionType.SessionCustomizationToggled]: true, [ActionType.SessionCustomizationUpdated]: false, [ActionType.SessionCustomizationRemoved]: false, [ActionType.SessionMcpServerStateChanged]: false, + [ActionType.SessionTruncated]: true, [ActionType.SessionIsReadChanged]: true, [ActionType.SessionIsArchivedChanged]: true, [ActionType.SessionActivityChanged]: false, [ActionType.SessionChangesetsChanged]: false, [ActionType.SessionConfigChanged]: true, [ActionType.SessionMetaChanged]: false, - [ActionType.ChatTurnStarted]: true, - [ActionType.ChatDelta]: false, - [ActionType.ChatResponsePart]: false, - [ActionType.ChatToolCallStart]: false, - [ActionType.ChatToolCallDelta]: false, - [ActionType.ChatToolCallReady]: false, - [ActionType.ChatToolCallConfirmed]: true, - [ActionType.ChatToolCallComplete]: true, - [ActionType.ChatToolCallResultConfirmed]: true, - [ActionType.ChatToolCallContentChanged]: true, - [ActionType.ChatTurnComplete]: false, - [ActionType.ChatTurnCancelled]: true, - [ActionType.ChatError]: false, - [ActionType.ChatUsage]: false, - [ActionType.ChatReasoning]: false, - [ActionType.ChatPendingMessageSet]: true, - [ActionType.ChatPendingMessageRemoved]: true, - [ActionType.ChatQueuedMessagesReordered]: true, - [ActionType.ChatInputRequested]: false, - [ActionType.ChatInputAnswerChanged]: true, - [ActionType.ChatInputCompleted]: true, - [ActionType.ChatTruncated]: true, [ActionType.ChangesetStatusChanged]: false, [ActionType.ChangesetFileSet]: false, [ActionType.ChangesetFileRemoved]: false, diff --git a/types/actions.ts b/types/actions.ts index a2469d66..66498160 100644 --- a/types/actions.ts +++ b/types/actions.ts @@ -10,7 +10,6 @@ export * from './common/actions.js'; export * from './channels-root/actions.js'; export * from './channels-session/actions.js'; -export * from './channels-chat/actions.js'; export * from './channels-terminal/actions.js'; export * from './channels-changeset/actions.js'; export * from './channels-annotations/actions.js'; diff --git a/types/channels-chat/actions.ts b/types/channels-chat/actions.ts deleted file mode 100644 index 8ce604f8..00000000 --- a/types/channels-chat/actions.ts +++ /dev/null @@ -1,544 +0,0 @@ -/** - * Chat Channel Actions — Mutations of an `ahp-chat:` channel's state. - * - * @module channels-chat/actions - */ - -import { ActionType } from '../common/actions.js'; -import type { StringOrMarkdown, ErrorInfo, FileEdit, UsageInfo } from '../common/state.js'; -import type { - Message, - ResponsePart, - ToolCallResult, - ToolResultContent, - ChatInputAnswer, - ChatInputRequest, - ChatInputResponseKind, - ConfirmationOption, - ToolCallContributor, -} from './state.js'; -import { - ToolCallConfirmationReason, - ToolCallCancellationReason, - PendingMessageKind, -} from './state.js'; - -// ─── Tool Call Action Base ─────────────────────────────────────────────────── - -/** - * Base interface for all tool-call-scoped actions, carrying the common turn - * and tool call identifiers. The owning chat URI is identified by the - * enclosing {@link ActionEnvelope}'s `channel` field. - * - * @category Chat Actions - */ -interface ToolCallActionBase { - /** Turn identifier */ - turnId: string; - /** Tool call identifier */ - toolCallId: string; - /** - * Additional provider-specific metadata for this tool call. - * - * Clients MAY look for well-known keys here to provide enhanced UI. - * For example, a `ptyTerminal` key with `{ input: string; output: string }` - * indicates the tool operated on a terminal (both `input` and `output` may - * contain escape sequences). - */ - _meta?: Record; -} - - -// ─── Chat Actions ─────────────────────────────────────────────────────────── - -/** - * A new message has been sent to the agent, and a new turn starts. - * - * A client is only allowed to send {@link MessageKind.User} messages. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatTurnStartedAction { - type: ActionType.ChatTurnStarted; - /** Turn identifier */ - turnId: string; - /** The new message */ - message: Message; - /** If this turn was auto-started from a queued message, the ID of that message */ - queuedMessageId?: string; -} - -/** - * Streaming text chunk from the assistant, appended to a specific response part. - * - * The server MUST first emit a `chat/responsePart` to create the target - * part (markdown or reasoning), then use this action to append text to it. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatDeltaAction { - type: ActionType.ChatDelta; - /** Turn identifier */ - turnId: string; - /** Identifier of the response part to append to */ - partId: string; - /** Text chunk */ - content: string; -} - -/** - * Structured content appended to the response. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatResponsePartAction { - type: ActionType.ChatResponsePart; - /** Turn identifier */ - turnId: string; - /** Response part (markdown or content ref) */ - part: ResponsePart; -} - -/** - * A tool call begins — parameters are streaming from the LM. - * - * The server sets {@link ToolCallContributor | `contributor`} to identify - * the origin of the tool. For client-provided tools, the named client is - * responsible for executing the tool once it reaches the `running` state - * and dispatching `chat/toolCallComplete`. For MCP-served tools, the - * server executes the call against the named `McpServerCustomization`. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatToolCallStartAction extends ToolCallActionBase { - type: ActionType.ChatToolCallStart; - /** Internal tool name (for debugging/logging) */ - toolName: string; - /** Human-readable tool name */ - displayName: string; - /** - * Reference to the contributor of the tool being called. Absent for - * server-side tools that are not contributed by a client or MCP server. - */ - contributor?: ToolCallContributor; -} - -/** - * Streaming partial parameters for a tool call. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatToolCallDeltaAction extends ToolCallActionBase { - type: ActionType.ChatToolCallDelta; - /** Partial parameter content to append */ - content: string; - /** Updated progress message */ - invocationMessage?: StringOrMarkdown; -} - -/** - * Tool call parameters are complete, or a running tool requires re-confirmation. - * - * When dispatched for a `streaming` tool call, transitions to `pending-confirmation` - * or directly to `running` if `confirmed` is set. - * - * When dispatched for a `running` tool call (e.g. mid-execution permission needed), - * transitions back to `pending-confirmation`. The `invocationMessage` and `_meta` - * SHOULD be updated to describe the specific confirmation needed. Clients use the - * standard `chat/toolCallConfirmed` flow to approve or deny. - * - * For client-provided tools, the server typically sets `confirmed` to - * `'not-needed'` so the tool transitions directly to `running`, where the - * owning client can begin execution immediately. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatToolCallReadyAction extends ToolCallActionBase { - type: ActionType.ChatToolCallReady; - /** Message describing what the tool will do or what confirmation is needed */ - invocationMessage: StringOrMarkdown; - /** Raw tool input */ - toolInput?: string; - /** Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) */ - confirmationTitle?: StringOrMarkdown; - /** File edits that this tool call will perform, for preview before confirmation */ - edits?: { items: FileEdit[] }; - /** Whether the agent host allows the client to edit the tool's input parameters before confirming */ - editable?: boolean; - /** If set, the tool was auto-confirmed and transitions directly to `running` */ - confirmed?: ToolCallConfirmationReason; - /** - * Options the server offers for this confirmation. When present, the client - * SHOULD render these instead of a plain approve/deny UI. Each option - * belongs to a {@link ConfirmationOptionGroup} so the client can still - * categorise the choices. - */ - options?: ConfirmationOption[]; -} - -/** - * Client approves a pending tool call. The tool transitions to `running`. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatToolCallApprovedAction extends ToolCallActionBase { - type: ActionType.ChatToolCallConfirmed; - /** The tool call was approved */ - approved: true; - /** How the tool was confirmed */ - confirmed: ToolCallConfirmationReason; - /** Edited tool input parameters, if the client modified them before confirming */ - editedToolInput?: string; - /** ID of the selected confirmation option, if the server provided options */ - selectedOptionId?: string; -} - -/** - * Client denies a pending tool call. The tool transitions to `cancelled`. - * - * For client-provided tools, the owning client MUST dispatch this if it does - * not recognize the tool or cannot execute it. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatToolCallDeniedAction extends ToolCallActionBase { - type: ActionType.ChatToolCallConfirmed; - /** The tool call was denied */ - approved: false; - /** Why the tool was cancelled */ - reason: ToolCallCancellationReason.Denied | ToolCallCancellationReason.Skipped; - /** What the user suggested doing instead */ - userSuggestion?: Message; - /** Optional explanation for the denial */ - reasonMessage?: StringOrMarkdown; - /** ID of the selected confirmation option, if the server provided options */ - selectedOptionId?: string; -} - -/** - * Client confirms or denies a pending tool call. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export type ChatToolCallConfirmedAction = - | ChatToolCallApprovedAction - | ChatToolCallDeniedAction; - -/** - * Tool execution finished. Transitions to `completed` or `pending-result-confirmation` - * if `requiresResultConfirmation` is `true`. - * - * For client-provided tools (where `toolClientId` is set on the tool call state), - * the owning client dispatches this action with the execution result. The server - * SHOULD reject this action if the dispatching client does not match `toolClientId`. - * - * Servers waiting on a client tool call MAY time out after a reasonable duration - * if the implementing client disconnects or becomes unresponsive, and dispatch - * this action with `result.success = false` and an appropriate error. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatToolCallCompleteAction extends ToolCallActionBase { - type: ActionType.ChatToolCallComplete; - /** Execution result */ - result: ToolCallResult; - /** If true, the result requires client approval before finalizing */ - requiresResultConfirmation?: boolean; -} - -/** - * Client approves or denies a tool's result. - * - * If `approved` is `false`, the tool transitions to `cancelled` with reason `result-denied`. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatToolCallResultConfirmedAction extends ToolCallActionBase { - type: ActionType.ChatToolCallResultConfirmed; - /** Whether the result was approved */ - approved: boolean; -} - -/** - * Partial content produced while a tool is still executing. - * - * Replaces the `content` array on the running tool call state. Clients can - * use this to display live feedback (e.g. a terminal reference) before the - * tool completes. - * - * For client-provided tools (where `toolClientId` is set on the tool call state), - * the owning client dispatches this action to stream intermediate content while - * executing. The server SHOULD reject this action if the dispatching client does - * not match `toolClientId`. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatToolCallContentChangedAction extends ToolCallActionBase { - type: ActionType.ChatToolCallContentChanged; - /** The current partial content for the running tool call */ - content: ToolResultContent[]; -} - -/** - * Turn finished — the assistant is idle. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatTurnCompleteAction { - type: ActionType.ChatTurnComplete; - /** Turn identifier */ - turnId: string; -} - -/** - * Turn was aborted; server stops processing. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatTurnCancelledAction { - type: ActionType.ChatTurnCancelled; - /** Turn identifier */ - turnId: string; -} - -/** - * Error during turn processing. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatErrorAction { - type: ActionType.ChatError; - /** Turn identifier */ - turnId: string; - /** Error details */ - error: ErrorInfo; -} - - -/** - * Token usage report for a turn. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatUsageAction { - type: ActionType.ChatUsage; - /** Turn identifier */ - turnId: string; - /** Token usage data */ - usage: UsageInfo; -} - -/** - * Reasoning/thinking text from the model, appended to a specific reasoning response part. - * - * The server MUST first emit a `chat/responsePart` to create the target - * reasoning part, then use this action to append text to it. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatReasoningAction { - type: ActionType.ChatReasoning; - /** Turn identifier */ - turnId: string; - /** Identifier of the reasoning response part to append to */ - partId: string; - /** Reasoning text chunk */ - content: string; -} - - -// ─── Truncation ────────────────────────────────────────────────────────────── - -/** - * Truncates a session's history. If `turnId` is provided, all turns after that - * turn are removed and the specified turn is kept. If `turnId` is omitted, all - * turns are removed. - * - * If there is an active turn it is silently dropped and the chat status - * returns to `idle`. - * - * Common use-case: truncate old data then dispatch a new - * `chat/turnStarted` with an edited message. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatTruncatedAction { - type: ActionType.ChatTruncated; - /** Keep turns up to and including this turn. Omit to clear all turns. */ - turnId?: string; -} - -// ─── Pending Message Actions ───────────────────────────────────────────────── - -/** - * A pending message was set (upsert semantics: creates or replaces). - * - * For steering messages, this always replaces the single steering message. - * For queued messages, if a message with the given `id` already exists it is - * updated in place; otherwise it is appended to the queue. If the chat is - * idle when a queued message is set, the server SHOULD immediately consume it - * and start a new turn. - * - * A client is only allowed to send {@link MessageKind.User} messages. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatPendingMessageSetAction { - type: ActionType.ChatPendingMessageSet; - /** Whether this is a steering or queued message */ - kind: PendingMessageKind; - /** Unique identifier for this pending message */ - id: string; - /** The message content */ - message: Message; -} - -/** - * A pending message was removed (steering or queued). - * - * Dispatched by clients to cancel a pending message, or by the server when - * it consumes a message (e.g. starting a turn from a queued message or - * injecting a steering message into the current turn). - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatPendingMessageRemovedAction { - type: ActionType.ChatPendingMessageRemoved; - /** Whether this is a steering or queued message */ - kind: PendingMessageKind; - /** Identifier of the pending message to remove */ - id: string; -} - -/** - * Reorder the queued messages. - * - * The `order` array contains the IDs of queued messages in their new - * desired order. IDs not present in the current queue are ignored. - * Queued messages whose IDs are absent from `order` are appended at - * the end in their original relative order (so a client with a stale - * view of the queue never silently drops messages). - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatQueuedMessagesReorderedAction { - type: ActionType.ChatQueuedMessagesReordered; - /** Queued message IDs in the desired order */ - order: string[]; -} - -// ─── Session Input Actions ────────────────────────────────────────────────── - -/** - * A session requested input from the user. - * - * Full-request upsert semantics: the `request` replaces any existing request - * with the same `id`, or is appended if it is new. Answer drafts are preserved - * unless `request.answers` is provided. - * - * @category Chat Actions - * @version 1 - */ -export interface ChatInputRequestedAction { - type: ActionType.ChatInputRequested; - /** Input request to create or replace */ - request: ChatInputRequest; -} - -/** - * A client updated, submitted, skipped, or removed a single in-progress answer. - * - * Dispatching with `answer: undefined` removes that question's answer draft. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatInputAnswerChangedAction { - type: ActionType.ChatInputAnswerChanged; - /** Input request identifier */ - requestId: string; - /** Question identifier within the input request */ - questionId: string; - /** Updated answer, or `undefined` to clear an answer draft */ - answer?: ChatInputAnswer; -} - -/** - * A client accepted, declined, or cancelled a session input request. - * - * If accepted, the server uses `answers` (when provided) plus the request's - * synced answer state to resume the blocked operation. - * - * @category Chat Actions - * @version 1 - * @clientDispatchable - */ -export interface ChatInputCompletedAction { - type: ActionType.ChatInputCompleted; - /** Input request identifier */ - requestId: string; - /** Completion outcome */ - response: ChatInputResponseKind; - /** Optional final answer replacement, keyed by question ID */ - answers?: Record; -} - - -export type ChatAction = - | ChatTurnStartedAction - | ChatDeltaAction - | ChatResponsePartAction - | ChatToolCallStartAction - | ChatToolCallDeltaAction - | ChatToolCallReadyAction - | ChatToolCallConfirmedAction - | ChatToolCallCompleteAction - | ChatToolCallResultConfirmedAction - | ChatToolCallContentChangedAction - | ChatTurnCompleteAction - | ChatTurnCancelledAction - | ChatErrorAction - | ChatUsageAction - | ChatReasoningAction - | ChatTruncatedAction - | ChatPendingMessageSetAction - | ChatPendingMessageRemovedAction - | ChatQueuedMessagesReorderedAction - | ChatInputRequestedAction - | ChatInputAnswerChangedAction - | ChatInputCompletedAction -; diff --git a/types/channels-chat/commands.ts b/types/channels-chat/commands.ts deleted file mode 100644 index c44957f3..00000000 --- a/types/channels-chat/commands.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Chat Channel Commands — `createChat` and `disposeChat`. - * - * @module channels-chat/commands - */ - -import type { URI } from '../common/state.js'; -import type { BaseParams } from '../common/commands.js'; -import type { ModelSelection } from '../channels-root/state.js'; -import type { AgentSelection } from '../channels-session/state.js'; -import type { Message } from './state.js'; - -// ─── createChat ────────────────────────────────────────────────────────────── - -/** - * Identifies a source chat and turn to fork from. - */ -export interface ChatForkSource { - /** URI of the existing chat to fork from */ - chat: URI; - /** Turn ID in the source chat; content up to and including this turn's response is copied */ - turnId: string; -} - -/** - * Creates a new chat within a session. - * - * @category Commands - * @method createChat - * @direction Client → Server - * @messageType Request - * @version 1 - */ -export interface CreateChatParams extends BaseParams { - /** Session URI containing the new chat. */ - channel: URI; - /** Chat URI (client-chosen, e.g. `ahp-chat:/`). */ - chat: URI; - /** Optional initial message for the new chat. */ - initialMessage?: Message; - /** Optional per-chat model override. */ - model?: ModelSelection; - /** Optional per-chat agent override. */ - agent?: AgentSelection; - /** Optional source chat and turn to fork from. */ - source?: ChatForkSource; -} - -// ─── disposeChat ───────────────────────────────────────────────────────────── - -/** - * Disposes a chat and cleans up server-side resources. - * - * @category Commands - * @method disposeChat - * @direction Client → Server - * @messageType Request - * @version 1 - */ -export interface DisposeChatParams extends BaseParams {} diff --git a/types/channels-chat/reducer.ts b/types/channels-chat/reducer.ts deleted file mode 100644 index 24d93841..00000000 --- a/types/channels-chat/reducer.ts +++ /dev/null @@ -1,663 +0,0 @@ -/** - * Chat Channel Reducer — Pure reducer for `ChatState`, including turn - * lifecycle, tool call transitions, pending messages, and input requests. - * - * @module channels-chat/reducer - */ - -import { ActionType } from '../common/actions.js'; -import type { - ChatInputRequest, - ChatState, - ToolCallState, - ResponsePart, - ToolCallResponsePart, - Turn, - PendingMessage, - ConfirmationOption, -} from './state.js'; -import { - TurnState, - ToolCallStatus, - ToolCallConfirmationReason, - ToolCallCancellationReason, - ResponsePartKind, - PendingMessageKind, -} from './state.js'; -import { SessionStatus } from '../channels-session/state.js'; -import type { ChatAction } from '../action-origin.generated.js'; -import { softAssertNever } from '../common/reducer-helpers.js'; - -// ─── Helpers ───────────────────────────────────────────────────────────────── - -/** Extracts the common base fields shared by all tool call lifecycle states. */ -function tcBase(tc: ToolCallState) { - return { - toolCallId: tc.toolCallId, - toolName: tc.toolName, - displayName: tc.displayName, - contributor: tc.contributor, - _meta: tc._meta, - }; -} - -function tcBaseWithMeta(tc: ToolCallState, meta: Record | undefined) { - return { - ...tcBase(tc), - _meta: meta ?? tc._meta, - }; -} - -/** Resolves a selected option from the confirmation options array by ID. */ -function resolveSelectedOption(options: ConfirmationOption[] | undefined, id: string | undefined): ConfirmationOption | undefined { - if (!id || !options) { - return undefined; - } - return options.find(o => o.id === id); -} - -/** Returns `true` if the active turn has any tool call awaiting user confirmation. */ -function hasPendingToolCallConfirmation(state: ChatState): boolean { - if (!state.activeTurn) { - return false; - } - return state.activeTurn.responseParts.some(part => - part.kind === ResponsePartKind.ToolCall - && (part.toolCall.status === ToolCallStatus.PendingConfirmation - || part.toolCall.status === ToolCallStatus.PendingResultConfirmation), - ); -} - -/** Bitmask covering the mutually-exclusive activity bits (bits 0–4). */ -const STATUS_ACTIVITY_MASK = (1 << 5) - 1; - -/** Sets or clears a metadata flag on a status value. */ -function withStatusFlag(status: SessionStatus, flag: SessionStatus, set: boolean): SessionStatus { - return set ? status | flag : status & ~flag; -} - -/** Derives the summary status from live session work, preserving orthogonal flags. */ -function summaryStatus(state: ChatState, terminalStatus?: SessionStatus.Error): SessionStatus { - let activity: SessionStatus; - if (terminalStatus) { - activity = terminalStatus; - } else if ((state.inputRequests?.length ?? 0) > 0 || hasPendingToolCallConfirmation(state)) { - activity = SessionStatus.InputNeeded; - } else if (state.activeTurn) { - activity = SessionStatus.InProgress; - } else { - activity = SessionStatus.Idle; - } - - return state.status & ~STATUS_ACTIVITY_MASK | activity; -} - -/** - * Returns a state with `status` recomputed. Use this after reducers - * that change data which feeds into {@link summaryStatus} (e.g. tool call - * lifecycle transitions that may enter or leave a pending-confirmation state). - */ -function refreshSummaryStatus(state: ChatState): ChatState { - const status = summaryStatus(state); - if (status === state.status) { - return state; - } - return { ...state, status }; -} - -/** - * Ends the active turn, finalizing it into a completed turn record. - * - * Tool call parts with non-terminal states are forced to cancelled. - * Pending permissions are stripped from tool call parts. - */ -function endTurn( - state: ChatState, - turnId: string, - turnState: TurnState, - terminalStatus?: SessionStatus.Error, - error?: { errorType: string; message: string; stack?: string }, -): ChatState { - if (!state.activeTurn || state.activeTurn.id !== turnId) { - return state; - } - const active = state.activeTurn; - - const responseParts: ResponsePart[] = active.responseParts.map(part => { - if (part.kind !== ResponsePartKind.ToolCall) { - return part; - } - const tc = part.toolCall; - if (tc.status === ToolCallStatus.Completed || tc.status === ToolCallStatus.Cancelled) { - return part; - } - // Force non-terminal tool calls into cancelled state - return { - kind: ResponsePartKind.ToolCall, - toolCall: { - status: ToolCallStatus.Cancelled as const, - ...tcBase(tc), - invocationMessage: tc.status === ToolCallStatus.Streaming ? (tc.invocationMessage ?? '') : tc.invocationMessage, - toolInput: tc.status === ToolCallStatus.Streaming ? undefined : tc.toolInput, - reason: ToolCallCancellationReason.Skipped, - }, - }; - }); - - const turn: Turn = { - id: active.id, - message: active.message, - responseParts, - usage: active.usage, - state: turnState, - error, - }; - - const next: ChatState = { - ...state, - turns: [...state.turns, turn], - activeTurn: undefined, - modifiedAt: new Date(Date.now()).toISOString(), - }; - delete next.inputRequests; - return { - ...next, - status: summaryStatus(next, terminalStatus), - }; -} - -function upsertInputRequest(state: ChatState, request: ChatInputRequest): ChatState { - const existing = state.inputRequests ?? []; - const idx = existing.findIndex(r => r.id === request.id); - const inputRequests = [...existing]; - if (idx >= 0) { - const answers = request.answers ?? inputRequests[idx].answers; - inputRequests[idx] = { ...request, answers }; - } else { - inputRequests.push(request); - } - const next = { ...state, inputRequests }; - return { ...next, status: withStatusFlag(summaryStatus(next), SessionStatus.IsRead, false), modifiedAt: new Date(Date.now()).toISOString() }; -} - -/** - * Immutably updates the tool call inside a `ToolCall` response part in the - * active turn's `responseParts` array. Returns `state` unchanged if the - * active turn or tool call doesn't match. - */ -function updateToolCallInParts( - state: ChatState, - turnId: string, - toolCallId: string, - updater: (tc: ToolCallState) => ToolCallState, -): ChatState { - const activeTurn = state.activeTurn; - if (!activeTurn || activeTurn.id !== turnId) { - return state; - } - - let found = false; - const responseParts = activeTurn.responseParts.map(part => { - if (part.kind === ResponsePartKind.ToolCall && part.toolCall.toolCallId === toolCallId) { - const updated = updater(part.toolCall); - if (updated === part.toolCall) { - return part; - } - found = true; - return { ...part, toolCall: updated }; - } - return part; - }); - - if (!found) { - return state; - } - - return { - ...state, - activeTurn: { ...activeTurn, responseParts }, - }; -} - -/** - * Immutably updates a response part by `partId` in the active turn. - * For markdown/reasoning parts, matches on `id`. For tool call parts, - * matches on `toolCall.toolCallId`. - */ -function updateResponsePart( - state: ChatState, - turnId: string, - partId: string, - updater: (part: ResponsePart) => ResponsePart, -): ChatState { - const activeTurn = state.activeTurn; - if (!activeTurn || activeTurn.id !== turnId) { - return state; - } - - let found = false; - const responseParts = activeTurn.responseParts.map(part => { - if (!found) { - const id = part.kind === ResponsePartKind.ToolCall - ? part.toolCall.toolCallId - : 'id' in part ? part.id : undefined; - if (id === partId) { - found = true; - return updater(part); - } - } - return part; - }); - - if (!found) { - return state; - } - - return { - ...state, - activeTurn: { ...activeTurn, responseParts }, - }; -} - - -// ─── Chat Reducer ──────────────────────────────────────────────────────────── - -/** - * Pure reducer for chat state. Handles all {@link ChatAction} variants. - */ -export function chatReducer(state: ChatState, action: ChatAction, log?: (msg: string) => void): ChatState { - switch (action.type) { - // ── Turn Lifecycle ──────────────────────────────────────────────────── - - case ActionType.ChatTurnStarted: { - let next: ChatState = { - ...state, - activeTurn: { - id: action.turnId, - message: action.message, - responseParts: [], - usage: undefined, - }, - }; - next = { - ...next, - status: withStatusFlag(summaryStatus(next), SessionStatus.IsRead, false), - modifiedAt: new Date(Date.now()).toISOString(), - }; - - // If this turn was auto-started from a pending message, remove it - if (action.queuedMessageId) { - if (next.steeringMessage?.id === action.queuedMessageId) { - next = { ...next, steeringMessage: undefined }; - } - if (next.queuedMessages) { - const filtered = next.queuedMessages.filter(m => m.id !== action.queuedMessageId); - next = { ...next, queuedMessages: filtered.length > 0 ? filtered : undefined }; - } - } - - return next; - } - - case ActionType.ChatDelta: - return updateResponsePart(state, action.turnId, action.partId, part => { - if (part.kind === ResponsePartKind.Markdown) { - return { ...part, content: part.content + action.content }; - } - return part; - }); - - case ActionType.ChatResponsePart: - if (!state.activeTurn || state.activeTurn.id !== action.turnId) { - return state; - } - return { - ...state, - activeTurn: { - ...state.activeTurn, - responseParts: [...state.activeTurn.responseParts, action.part], - }, - }; - - case ActionType.ChatTurnComplete: - return endTurn(state, action.turnId, TurnState.Complete); - - case ActionType.ChatTurnCancelled: - return endTurn(state, action.turnId, TurnState.Cancelled); - - case ActionType.ChatError: - return endTurn(state, action.turnId, TurnState.Error, SessionStatus.Error, action.error); - - // ── Tool Call State Machine ─────────────────────────────────────────── - - case ActionType.ChatToolCallStart: - if (!state.activeTurn || state.activeTurn.id !== action.turnId) { - return state; - } - return { - ...state, - activeTurn: { - ...state.activeTurn, - responseParts: [ - ...state.activeTurn.responseParts, - { - kind: ResponsePartKind.ToolCall, - toolCall: { - toolCallId: action.toolCallId, - toolName: action.toolName, - displayName: action.displayName, - contributor: action.contributor, - _meta: action._meta, - status: ToolCallStatus.Streaming, - }, - } satisfies ToolCallResponsePart, - ], - }, - }; - - case ActionType.ChatToolCallDelta: - return updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { - if (tc.status !== ToolCallStatus.Streaming) { - return tc; - } - return { - ...tc, - ...(action._meta !== undefined ? { _meta: action._meta } : {}), - partialInput: (tc.partialInput ?? '') + action.content, - invocationMessage: action.invocationMessage ?? tc.invocationMessage, - }; - }); - - case ActionType.ChatToolCallReady: - return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { - if (tc.status !== ToolCallStatus.Streaming && tc.status !== ToolCallStatus.Running) { - return tc; - } - const base = tcBaseWithMeta(tc, action._meta); - if (action.confirmed) { - return { - status: ToolCallStatus.Running, - ...base, - invocationMessage: action.invocationMessage, - toolInput: action.toolInput, - confirmed: action.confirmed, - }; - } - return { - status: ToolCallStatus.PendingConfirmation, - ...base, - invocationMessage: action.invocationMessage, - toolInput: action.toolInput, - confirmationTitle: action.confirmationTitle, - edits: action.edits, - editable: action.editable, - ...(action.options ? { options: action.options } : {}), - }; - })); - - case ActionType.ChatToolCallConfirmed: - return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { - if (tc.status !== ToolCallStatus.PendingConfirmation) { - return tc; - } - const base = tcBaseWithMeta(tc, action._meta); - const selectedOption = resolveSelectedOption(tc.options, action.selectedOptionId); - if (action.approved) { - return { - status: ToolCallStatus.Running, - ...base, - invocationMessage: tc.invocationMessage, - toolInput: action.editedToolInput ?? tc.toolInput, - confirmed: action.confirmed, - ...(selectedOption ? { selectedOption } : {}), - }; - } - return { - status: ToolCallStatus.Cancelled, - ...base, - invocationMessage: tc.invocationMessage, - toolInput: tc.toolInput, - reason: action.reason, - reasonMessage: action.reasonMessage, - userSuggestion: action.userSuggestion, - ...(selectedOption ? { selectedOption } : {}), - }; - })); - - case ActionType.ChatToolCallComplete: - return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { - if (tc.status !== ToolCallStatus.Running && tc.status !== ToolCallStatus.PendingConfirmation) { - return tc; - } - const base = tcBaseWithMeta(tc, action._meta); - const confirmed = tc.status === ToolCallStatus.Running - ? tc.confirmed - : ToolCallConfirmationReason.NotNeeded; - const selectedOption = tc.status === ToolCallStatus.Running - ? tc.selectedOption - : undefined; - if (action.requiresResultConfirmation) { - return { - status: ToolCallStatus.PendingResultConfirmation, - ...base, - invocationMessage: tc.invocationMessage, - toolInput: tc.toolInput, - confirmed, - ...(selectedOption ? { selectedOption } : {}), - ...action.result, - }; - } - return { - status: ToolCallStatus.Completed, - ...base, - invocationMessage: tc.invocationMessage, - toolInput: tc.toolInput, - confirmed, - ...(selectedOption ? { selectedOption } : {}), - ...action.result, - }; - })); - - case ActionType.ChatToolCallResultConfirmed: - return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { - if (tc.status !== ToolCallStatus.PendingResultConfirmation) { - return tc; - } - const base = tcBaseWithMeta(tc, action._meta); - if (action.approved) { - return { - status: ToolCallStatus.Completed, - ...base, - invocationMessage: tc.invocationMessage, - toolInput: tc.toolInput, - confirmed: tc.confirmed, - ...(tc.selectedOption ? { selectedOption: tc.selectedOption } : {}), - success: tc.success, - pastTenseMessage: tc.pastTenseMessage, - content: tc.content, - structuredContent: tc.structuredContent, - error: tc.error, - }; - } - return { - status: ToolCallStatus.Cancelled, - ...base, - invocationMessage: tc.invocationMessage, - toolInput: tc.toolInput, - reason: ToolCallCancellationReason.ResultDenied, - ...(tc.selectedOption ? { selectedOption: tc.selectedOption } : {}), - }; - })); - - case ActionType.ChatToolCallContentChanged: - return updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { - if (tc.status !== ToolCallStatus.Running) { - return tc; - } - return { - ...tc, - ...(action._meta !== undefined ? { _meta: action._meta } : {}), - content: action.content, - }; - }); - - - case ActionType.ChatUsage: - if (!state.activeTurn || state.activeTurn.id !== action.turnId) { - return state; - } - return { - ...state, - activeTurn: { ...state.activeTurn, usage: action.usage }, - }; - - case ActionType.ChatReasoning: - return updateResponsePart(state, action.turnId, action.partId, part => { - if (part.kind === ResponsePartKind.Reasoning) { - return { ...part, content: part.content + action.content }; - } - return part; - }); - - - // ── Truncation ──────────────────────────────────────────────────────── - - case ActionType.ChatTruncated: { - let turns: typeof state.turns; - if (action.turnId === undefined) { - turns = []; - } else { - const idx = state.turns.findIndex(t => t.id === action.turnId); - if (idx < 0) { - return state; - } - turns = state.turns.slice(0, idx + 1); - } - const next: ChatState = { - ...state, - turns, - activeTurn: undefined, - modifiedAt: new Date(Date.now()).toISOString(), - }; - delete next.inputRequests; - return { - ...next, - status: summaryStatus(next), - }; - } - - // ── Session Input Requests ───────────────────────────────────────────── - - case ActionType.ChatInputRequested: - return upsertInputRequest(state, action.request); - - case ActionType.ChatInputAnswerChanged: { - const existing = state.inputRequests; - const idx = existing?.findIndex(request => request.id === action.requestId) ?? -1; - if (!existing || idx < 0) { - return state; - } - const request = existing[idx]; - const answers = { ...(request.answers ?? {}) }; - if (action.answer === undefined) { - delete answers[action.questionId]; - } else { - answers[action.questionId] = action.answer; - } - const updated = [...existing]; - updated[idx] = { - ...request, - answers: Object.keys(answers).length > 0 ? answers : undefined, - }; - return { - ...state, - inputRequests: updated, - modifiedAt: new Date(Date.now()).toISOString(), - }; - } - - case ActionType.ChatInputCompleted: { - const existing = state.inputRequests; - if (!existing?.some(request => request.id === action.requestId)) { - return state; - } - const inputRequests = existing.filter(request => request.id !== action.requestId); - const next: ChatState = { - ...state, - }; - if (inputRequests.length > 0) { - next.inputRequests = inputRequests; - } else { - delete next.inputRequests; - } - return { - ...next, - status: summaryStatus(next), - modifiedAt: new Date(Date.now()).toISOString(), - }; - } - - // ── Pending Messages ────────────────────────────────────────────────── - - case ActionType.ChatPendingMessageSet: { - const entry: PendingMessage = { id: action.id, message: action.message }; - if (action.kind === PendingMessageKind.Steering) { - return { ...state, steeringMessage: entry }; - } - const existing = state.queuedMessages ?? []; - const idx = existing.findIndex(m => m.id === action.id); - if (idx >= 0) { - const updated = [...existing]; - updated[idx] = entry; - return { ...state, queuedMessages: updated }; - } - return { ...state, queuedMessages: [...existing, entry] }; - } - - case ActionType.ChatPendingMessageRemoved: { - if (action.kind === PendingMessageKind.Steering) { - if (!state.steeringMessage || state.steeringMessage.id !== action.id) { - return state; - } - return { ...state, steeringMessage: undefined }; - } - const existing = state.queuedMessages; - if (!existing) { - return state; - } - const filtered = existing.filter(m => m.id !== action.id); - return filtered.length === existing.length - ? state - : { ...state, queuedMessages: filtered.length > 0 ? filtered : undefined }; - } - - case ActionType.ChatQueuedMessagesReordered: { - const existing = state.queuedMessages; - if (!existing) { - return state; - } - const byId = new Map(existing.map(m => [m.id, m])); - const ordered = new Set(); - const reordered = action.order - .filter(id => { - if (byId.has(id) && !ordered.has(id)) { - ordered.add(id); - return true; - } - return false; - }) - .map(id => byId.get(id)!); - // Append any messages not mentioned in order, preserving original order - for (const m of existing) { - if (!ordered.has(m.id)) { - reordered.push(m); - } - } - return { ...state, queuedMessages: reordered }; - } - - default: - softAssertNever(action, log); - return state; - } -} diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts deleted file mode 100644 index 1b93dcd7..00000000 --- a/types/channels-chat/state.ts +++ /dev/null @@ -1,1149 +0,0 @@ -/** - * Chat State Types — Per-chat turns, messages, response parts, tool calls, - * and elicitation/input requests exposed on `ahp-chat:` channels. - * - * @module channels-chat/state - */ - -import type { ModelSelection } from '../channels-root/state.js'; -import type { AgentSelection, SessionStatus } from '../channels-session/state.js'; -import type { - ContentRef, - ErrorInfo, - FileEdit, - StringOrMarkdown, - TextRange, - TextSelection, - URI, - UsageInfo, -} from '../common/state.js'; - -// ─── Chat State ────────────────────────────────────────────────────────────── - -/** - * Full state for a single chat, loaded when a client subscribes to the chat's - * URI. - * - * The lightweight catalog representation of a chat is {@link ChatSummary}, - * carried in {@link SessionState.chats | `SessionState.chats`}. `ChatState` - * **denormalizes** every {@link ChatSummary} field directly onto itself so - * subscribers receive one flat object instead of having to merge a nested - * `summary` sub-object. Producers MUST keep the two representations - * consistent: any change to the inlined fields below SHOULD also be - * announced on the parent session via the matching - * {@link SessionChatUpdatedAction | `session/chatUpdated`} action. - * - * @category Chat State - */ -export interface ChatState { - // ── Summary fields (denormalized from ChatSummary) ───────────────── - /** Chat URI */ - resource: URI; - /** Chat title */ - title: string; - /** Current chat status (reuses SessionStatus shape) */ - status: SessionStatus; - /** Human-readable description of what the chat is currently doing */ - activity?: string; - /** Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) */ - modifiedAt: string; - /** Optional per-chat model override (defaults to the session's model) */ - model?: ModelSelection; - /** Optional per-chat agent override (defaults to the session's agent) */ - agent?: AgentSelection; - /** How this chat came into existence */ - origin?: ChatOrigin; - /** - * Optional per-chat working directory. - * - * If absent, the chat inherits - * {@link SessionSummary.workingDirectory | the session's working directory}. - * Hosts MAY override this for individual chats — for example, to give a - * subordinate chat its own git worktree so multiple chats in a session can - * make independent edits that the orchestrator later merges back. - */ - workingDirectory?: URI; - - // ── Conversation contents ────────────────────────────────────────── - /** Completed turns */ - turns: Turn[]; - /** Currently in-progress turn */ - activeTurn?: ActiveTurn; - /** Message to inject into the current turn at a convenient point */ - steeringMessage?: PendingMessage; - /** Messages to send automatically as new turns after the current turn finishes */ - queuedMessages?: PendingMessage[]; - /** Requests for user input that are currently blocking or informing chat progress */ - inputRequests?: ChatInputRequest[]; - /** - * Additional provider-specific metadata for this chat. - */ - _meta?: Record; -} - -/** - * Lightweight catalog entry for a chat, carried in - * {@link SessionState.chats | `SessionState.chats`}. The full conversation - * lives in {@link ChatState}, which inlines (denormalizes) every field below. - * - * @category Chat State - */ -export interface ChatSummary { - /** Chat URI */ - resource: URI; - /** Chat title */ - title: string; - /** Current chat status (reuses SessionStatus shape) */ - status: SessionStatus; - /** Human-readable description of what the chat is currently doing */ - activity?: string; - /** Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) */ - modifiedAt: string; - /** Optional per-chat model override (defaults to the session's model) */ - model?: ModelSelection; - /** Optional per-chat agent override (defaults to the session's agent) */ - agent?: AgentSelection; - /** How this chat came into existence */ - origin?: ChatOrigin; - /** - * Optional per-chat working directory. - * - * If absent, the chat inherits - * {@link SessionSummary.workingDirectory | the session's working directory}. - * See {@link ChatState.workingDirectory} for usage notes. - */ - workingDirectory?: URI; -} - -export const enum ChatOriginKind { - User = 'user', - Fork = 'fork', - Tool = 'tool', -} - -export type ChatOrigin = - | { kind: ChatOriginKind.User } - | { kind: ChatOriginKind.Fork; chat: URI; turnId: string } - | { kind: ChatOriginKind.Tool; chat: URI; toolCallId: string }; - -// ─── Pending Message Types ─────────────────────────────────────────────────── - -/** - * Discriminant for pending message kinds. - * - * @category Pending Message Types - */ -export const enum PendingMessageKind { - /** Injected into the current turn at a convenient point */ - Steering = 'steering', - /** Sent automatically as a new turn after the current turn finishes */ - Queued = 'queued', -} - -/** - * A message queued for future delivery to the agent. - * - * Steering messages are injected into the current turn mid-flight. - * Queued messages are automatically started as new turns after the - * current turn naturally finishes. - * - * @category Pending Message Types - */ -export interface PendingMessage { - /** Unique identifier for this pending message */ - id: string; - /** The message that will start the next turn */ - message: Message; -} - - -// ─── Chat Input Types ──────────────────────────────────────────────────── - -/** - * How a client completed an input request. - * - * @category Chat Input Types - */ -export const enum ChatInputResponseKind { - Accept = 'accept', - Decline = 'decline', - Cancel = 'cancel', -} - -/** - * Question/input control kind. - * - * @category Chat Input Types - */ -export const enum ChatInputQuestionKind { - Text = 'text', - Number = 'number', - Integer = 'integer', - Boolean = 'boolean', - SingleSelect = 'single-select', - MultiSelect = 'multi-select', -} - -/** - * A choice in a select-style question. - * - * @category Chat Input Types - */ -export interface ChatInputOption { - /** Stable option identifier; for MCP enum values this is the enum string */ - id: string; - /** Display label */ - label: string; - /** Optional secondary text */ - description?: string; - /** Whether this option is the recommended/default choice */ - recommended?: boolean; -} - -interface ChatInputQuestionBase { - /** Stable question identifier used as the key in `answers` */ - id: string; - /** Short display title */ - title?: string; - /** Prompt shown to the user */ - message: string; - /** Whether the user must answer this question to accept the request */ - required?: boolean; -} - -/** Text question within a chat input request. */ -export interface ChatInputTextQuestion extends ChatInputQuestionBase { - kind: ChatInputQuestionKind.Text; - /** Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` */ - format?: string; - /** Minimum string length */ - min?: number; - /** Maximum string length */ - max?: number; - /** Default text */ - defaultValue?: string; -} - -/** Numeric question within a chat input request. */ -export interface ChatInputNumberQuestion extends ChatInputQuestionBase { - kind: ChatInputQuestionKind.Number | ChatInputQuestionKind.Integer; - /** - * Minimum value - * @format float - */ - min?: number; - /** - * Maximum value - * @format float - */ - max?: number; - /** - * Default numeric value - * @format float - */ - defaultValue?: number; -} - -/** Boolean question within a chat input request. */ -export interface ChatInputBooleanQuestion extends ChatInputQuestionBase { - kind: ChatInputQuestionKind.Boolean; - /** Default boolean value */ - defaultValue?: boolean; -} - -/** Single-select question within a chat input request. */ -export interface ChatInputSingleSelectQuestion extends ChatInputQuestionBase { - kind: ChatInputQuestionKind.SingleSelect; - /** Options the user may select from */ - options: ChatInputOption[]; - /** Whether the user may enter text instead of selecting an option */ - allowFreeformInput?: boolean; -} - -/** Multi-select question within a chat input request. */ -export interface ChatInputMultiSelectQuestion extends ChatInputQuestionBase { - kind: ChatInputQuestionKind.MultiSelect; - /** Options the user may select from */ - options: ChatInputOption[]; - /** Whether the user may enter text in addition to selecting options */ - allowFreeformInput?: boolean; - /** Minimum selected item count */ - min?: number; - /** Maximum selected item count */ - max?: number; -} - -/** - * One question within a chat input request. - * - * @category Chat Input Types - */ -export type ChatInputQuestion = ChatInputTextQuestion - | ChatInputNumberQuestion - | ChatInputBooleanQuestion - | ChatInputSingleSelectQuestion - | ChatInputMultiSelectQuestion; - -/** - * A live request for user input. - * - * The server creates or replaces requests with `chat/inputRequested`. - * Clients sync drafts with `chat/inputAnswerChanged` and complete requests - * with `chat/inputCompleted`. - * - * @category Chat Input Types - */ -export interface ChatInputRequest { - /** Stable request identifier */ - id: string; - /** Display message for the request as a whole */ - message?: string; - /** URL the user should review or open, for URL-style elicitations */ - url?: URI; - /** Ordered questions to ask the user */ - questions?: ChatInputQuestion[]; - /** Current draft or submitted answers, keyed by question ID */ - answers?: Record; -} - -/** - * Answer value kind. - * - * @category Chat Input Types - */ -export const enum ChatInputAnswerValueKind { - Text = 'text', - Number = 'number', - Boolean = 'boolean', - Selected = 'selected', - SelectedMany = 'selected-many', -} - -/** - * Value captured for one answer. - * - * @category Chat Input Types - */ -export interface ChatInputTextAnswerValue { - kind: ChatInputAnswerValueKind.Text; - value: string; -} - -export interface ChatInputNumberAnswerValue { - kind: ChatInputAnswerValueKind.Number; - /** @format float */ - value: number; -} - -export interface ChatInputBooleanAnswerValue { - kind: ChatInputAnswerValueKind.Boolean; - value: boolean; -} - -export interface ChatInputSelectedAnswerValue { - kind: ChatInputAnswerValueKind.Selected; - value: string; - /** Free-form text entered instead of selecting an option */ - freeformValues?: string[]; -} - -export interface ChatInputSelectedManyAnswerValue { - kind: ChatInputAnswerValueKind.SelectedMany; - value: string[]; - /** Free-form text entered in addition to selected options */ - freeformValues?: string[]; -} - -export type ChatInputAnswerValue = ChatInputTextAnswerValue - | ChatInputNumberAnswerValue - | ChatInputBooleanAnswerValue - | ChatInputSelectedAnswerValue - | ChatInputSelectedManyAnswerValue; - -export interface ChatInputAnswered { - /** Answer state */ - state: ChatInputAnswerState.Draft | ChatInputAnswerState.Submitted; - /** Answer value */ - value: ChatInputAnswerValue; -} - -export interface ChatInputSkipped { - /** Answer state */ - state: ChatInputAnswerState.Skipped; - /** Free-form reason or value captured while skipping, if any */ - freeformValues?: string[]; -} - -/** - * Answer lifecycle state. - * - * @category Chat Input Types - */ -export const enum ChatInputAnswerState { - Draft = 'draft', - Submitted = 'submitted', - Skipped = 'skipped', -} - -/** - * Draft, submitted, or skipped answer for one question. - * - * @category Chat Input Types - */ -export type ChatInputAnswer = ChatInputAnswered | ChatInputSkipped; - - -// ─── Turn Types ────────────────────────────────────────────────────────────── - -/** - * How a turn ended. - * - * @category Turn Types - */ -export const enum TurnState { - Complete = 'complete', - Cancelled = 'cancelled', - Error = 'error', -} - -/** - * Discriminant for {@link MessageAttachment} variants. - * - * @category Turn Types - */ -export const enum MessageAttachmentKind { - /** A simple, opaque attachment whose representation is described by the producer. */ - Simple = 'simple', - /** An attachment whose data is embedded inline as a base64 string. */ - EmbeddedResource = 'embeddedResource', - /** An attachment that references a resource by URI. */ - Resource = 'resource', - /** An attachment that references annotations on an annotations channel. */ - Annotations = 'annotations', -} - -/** - * A completed request/response cycle. - * - * @category Turn Types - */ -export interface Turn { - /** Turn identifier */ - id: string; - /** The message that initiated the turn */ - message: Message; - /** - * All response content in stream order: text, tool calls, reasoning, and content refs. - * - * Consumers should derive display text by concatenating markdown parts, - * and find tool calls by filtering for `ToolCall` parts. - */ - responseParts: ResponsePart[]; - /** Token usage info */ - usage: UsageInfo | undefined; - /** How the turn ended */ - state: TurnState; - /** Error details if state is `'error'` */ - error?: ErrorInfo; -} - -/** - * An in-progress turn — the assistant is actively streaming. - * - * @category Turn Types - */ -export interface ActiveTurn { - /** Turn identifier */ - id: string; - /** The message that initiated the turn */ - message: Message; - /** - * All response content in stream order: text, tool calls, reasoning, and content refs. - * - * Tool call parts include `pendingPermissions` when permissions are awaiting user approval. - */ - responseParts: ResponsePart[]; - /** Token usage info */ - usage: UsageInfo | undefined; -} - -/** - * Discriminant for Message types. - * - * @category Turn Types - */ -export enum MessageKind { - User = 'user', - SystemNotification = 'systemNotification', -} - -/** - * A message that initiates or steers a turn. Messages can originate from the - * user or be system-generated (see {@link MessageKind}). - * - * Attachments MAY be referenced inside {@link Message.text} via their - * {@link MessageAttachmentBase.range} field. Attachments without a range are - * still associated with the message but do not correspond to a specific span - * in the text. - * - * @category Turn Types - */ -export interface Message { - /** Message text */ - text: string; - /** The origin of the message */ - origin: { kind: MessageKind }; - /** File/selection attachments */ - attachments?: MessageAttachment[]; - /** - * Additional provider-specific metadata for this message. - * - * Clients MAY look for well-known keys here to provide enhanced UI, and - * agent hosts MAY use it to carry context that does not fit any other - * field. Mirrors the MCP `_meta` convention. - */ - _meta?: Record; -} - -/** - * Common fields shared by all {@link MessageAttachment} variants. - * - * @category Turn Types - */ -export interface MessageAttachmentBase { - /** - * A human-readable label for the attachment (e.g. the filename of a file - * attachment). Used for display in UI. - */ - label: string; - - /** - * If defined, the range in {@link Message.text} that references this - * attachment. This is a text range, not a byte range. - */ - range?: TextRange; - - /** - * Advisory display hint for clients rendering this attachment. Recognized - * values include: - * - * - `'image'`: the attachment is an image - * - `'document'`: the attachment is a textual document - * - `'symbol'`: the attachment is a code symbol (e.g. a function or class) - * - `'directory'`: the attachment is a folder - * - `'selection'`: the attachment is a selection within a document - * - * Implementations MAY provide additional values; clients SHOULD fall back - * to a reasonable default when an unknown value is encountered. - */ - displayKind?: string; - - /** - * Additional implementation-defined metadata for the attachment. - * - * If the attachment was produced by the `completions` command, the client - * MUST preserve every property of `_meta` originally returned by the agent - * host when sending the user message containing the accepted completion. - */ - _meta?: Record; -} - -/** - * A simple, opaque attachment whose model representation is described by - * the producer. - * - * @category Turn Types - */ -export interface SimpleMessageAttachment extends MessageAttachmentBase { - /** Discriminant */ - type: MessageAttachmentKind.Simple; - - /** - * Representation of the attachment as it should be shown to the model. - * - * If the attachment was produced by the client, this property MUST be - * defined so the agent host can correctly interpret the attachment. This - * property MAY be omitted when the attachment originated from a - * `completions` response. - */ - modelRepresentation?: string; -} - -/** - * An attachment whose data is embedded inline as a base64 string. - * - * Use this for small binary payloads (e.g. a pasted image) that should be - * delivered with the user message itself rather than fetched separately. - * - * @category Turn Types - */ -export interface MessageEmbeddedResourceAttachment extends MessageAttachmentBase { - /** Discriminant */ - type: MessageAttachmentKind.EmbeddedResource; - /** Base64-encoded binary data */ - data: string; - /** Content MIME type (e.g. `"image/png"`, `"application/pdf"`) */ - contentType: string; - /** - * Optional selection within the attached textual resource. - * - * Only meaningful for textual resources. - */ - selection?: TextSelection; -} - -/** - * An attachment that references a resource by URI. The content is not - * delivered inline; consumers can fetch it via `resourceRead` when needed. - * - * @category Turn Types - */ -export interface MessageResourceAttachment extends MessageAttachmentBase, ContentRef { - /** Discriminant */ - type: MessageAttachmentKind.Resource; - /** - * Optional selection within the referenced textual resource. - * - * Only meaningful for textual resources. - */ - selection?: TextSelection; -} - -/** - * An attachment that references annotations on a session's annotations - * channel (see {@link AnnotationsState}). - * - * When {@link annotationIds} is omitted the attachment references every - * annotation on the channel; when present it references only the listed - * {@link Annotation.id | annotation ids}. - * - * @category Turn Types - */ -export interface MessageAnnotationsAttachment extends MessageAttachmentBase { - /** Discriminant */ - type: MessageAttachmentKind.Annotations; - /** - * The annotations channel URI (typically `ahp-session://annotations`). - * Matches {@link AnnotationsSummary.resource}. - */ - resource: URI; - /** - * Specific {@link Annotation.id | annotation ids} to reference. When - * omitted, the attachment references all annotations on the channel. - */ - annotationIds?: string[]; -} - -/** - * An attachment associated with a {@link Message}. - * - * @category Turn Types - */ -export type MessageAttachment = - | SimpleMessageAttachment - | MessageEmbeddedResourceAttachment - | MessageResourceAttachment - | MessageAnnotationsAttachment; - -// ─── Response Parts ────────────────────────────────────────────────────────── - -/** - * Discriminant for response part types. - * - * @category Response Parts - */ -export const enum ResponsePartKind { - Markdown = 'markdown', - ContentRef = 'contentRef', - ToolCall = 'toolCall', - Reasoning = 'reasoning', - SystemNotification = 'systemNotification', -} - -/** - * @category Response Parts - */ -export interface MarkdownResponsePart { - /** Discriminant */ - kind: ResponsePartKind.Markdown; - /** Part identifier, used by `chat/delta` to target this part for content appends */ - id: string; - /** Markdown content */ - content: string; -} - -/** - * A content part that's a reference to large content stored outside the state tree. - * - * @category Response Parts - */ -export interface ResourceReponsePart extends ContentRef { - /** Discriminant */ - kind: ResponsePartKind.ContentRef; -} - -/** - * A tool call represented as a response part. - * - * Tool calls are part of the response stream, interleaved with text and - * reasoning. The `toolCall.toolCallId` serves as the part identifier for - * actions that target this part. - * - * @category Response Parts - */ -export interface ToolCallResponsePart { - /** Discriminant */ - kind: ResponsePartKind.ToolCall; - /** Full tool call lifecycle state */ - toolCall: ToolCallState; -} - -/** - * Reasoning/thinking content from the model. - * - * @category Response Parts - */ -export interface ReasoningResponsePart { - /** Discriminant */ - kind: ResponsePartKind.Reasoning; - /** Part identifier, used by `chat/reasoning` to target this part for content appends */ - id: string; - /** Accumulated reasoning text */ - content: string; -} - -/** - * @category Response Parts - */ -export type ResponsePart = - | MarkdownResponsePart - | ResourceReponsePart - | ToolCallResponsePart - | ReasoningResponsePart - | SystemNotificationResponsePart; - -/** - * A system notification surfaced as part of the response stream. - * - * System notifications are messages authored by the agent harness - * that need to be visible to both the agent (for situational awareness) and - * the user (for transcript continuity). Examples include "background subagent - * X completed" or "task Y was cancelled". - * - * @category Response Parts - */ -export interface SystemNotificationResponsePart { - /** Discriminant */ - kind: ResponsePartKind.SystemNotification; - /** The text of the system notification */ - content: StringOrMarkdown; -} - - -// ─── Tool Call Types ───────────────────────────────────────────────────────── - -/** - * Status of a tool call in the lifecycle state machine. - * - * @category Tool Call Types - */ -export const enum ToolCallStatus { - Streaming = 'streaming', - PendingConfirmation = 'pending-confirmation', - Running = 'running', - PendingResultConfirmation = 'pending-result-confirmation', - Completed = 'completed', - Cancelled = 'cancelled', -} - -/** - * How a tool call was confirmed for execution. - * - * - `NotNeeded` — No confirmation required (auto-approved) - * - `UserAction` — User explicitly approved - * - `Setting` — Approved by a persistent user setting - * - * @category Tool Call Types - */ -export const enum ToolCallConfirmationReason { - NotNeeded = 'not-needed', - UserAction = 'user-action', - Setting = 'setting', -} - -/** - * Why a tool call was cancelled. - * - * @category Tool Call Types - */ -export const enum ToolCallCancellationReason { - Denied = 'denied', - Skipped = 'skipped', - ResultDenied = 'result-denied', -} - -/** - * Whether a confirmation option represents an approval or denial action. - * - * @category Tool Call Types - */ -export const enum ConfirmationOptionKind { - Approve = 'approve', - Deny = 'deny', -} - -/** - * A confirmation option that the server offers for a tool call awaiting - * approval. Allows richer choices beyond simple approve/deny — for example, - * "Approve in this Session" or "Deny with reason." - * - * @category Tool Call Types - */ -export interface ConfirmationOption { - /** Unique identifier for the option, returned in the confirmed action */ - id: string; - /** Human-readable label displayed to the user */ - label: string; - /** Whether this option represents an approval or denial */ - kind: ConfirmationOptionKind; - /** - * Logical group number for visual categorisation. - * - * Clients SHOULD display options in the order they are defined and MAY - * use differing group numbers to insert dividers between logical clusters - * of options. - */ - group?: number; -} - -export const enum ToolCallContributorKind { - Client = 'client', - MCP = 'mcp', -} - -export interface ToolCallClientContributor { - kind: ToolCallContributorKind.Client; - /** - * If this tool is provided by a client, the `clientId` of the owning client. - * Absent for server-side tools. - * - * When set, the identified client is responsible for executing the tool and - * dispatching `chat/toolCallComplete` with the result. - */ - clientId: string; -} - -export interface ToolCallMcpContributor { - kind: ToolCallContributorKind.MCP; - /** - * Customization ID of the corresponding MCP server in {@link SessionState.customizations}. - */ - customizationId: string; -} - -export type ToolCallContributor = ToolCallClientContributor | ToolCallMcpContributor; - -/** - * Metadata common to all tool call states. - * - * @category Tool Call Types - * @remarks - * Fields like `toolName` carry agent-specific identifiers on the wire despite the - * agent-agnostic design principle. These exist for debugging and logging purposes. - * A future version may move these to a separate diagnostic channel or namespace them - * more clearly. - */ -interface ToolCallBase { - /** Unique tool call identifier */ - toolCallId: string; - /** Internal tool name (for debugging/logging) */ - toolName: string; - /** Human-readable tool name */ - displayName: string; - /** - * Reference to the contributor of the tool being called. - */ - contributor?: ToolCallContributor; - /** - * Additional provider-specific metadata for this tool call. - * - * This MAY include a `ui` field corresponding to the MCP Apps (SEP-1865) - * `McpUiToolMeta` found in MCP tool calls, which may be used in combination - * with the {@link contributor} to serve MCP Apps. - */ - _meta?: Record; -} - -/** - * Properties available once tool call parameters are fully received. - * - * @category Tool Call Types - */ -interface ToolCallParameterFields { - /** Message describing what the tool will do */ - invocationMessage: StringOrMarkdown; - /** Raw tool input */ - toolInput?: string; -} - -/** - * Tool execution result details, available after execution completes. - * - * @category Tool Call Types - */ -export interface ToolCallResult { - /** Whether the tool succeeded */ - success: boolean; - /** Past-tense description of what the tool did */ - pastTenseMessage: StringOrMarkdown; - /** - * Unstructured result content blocks. - * - * This mirrors the `content` field of MCP `CallToolResult`. - */ - content?: ToolResultContent[]; - /** - * Optional structured result object. - * - * This mirrors the `structuredContent` field of MCP `CallToolResult`. - */ - structuredContent?: Record; - /** Error details if the tool failed */ - error?: { message: string; code?: string }; -} - -/** - * LM is streaming the tool call parameters. - * - * @category Tool Call Types - */ -export interface ToolCallStreamingState extends ToolCallBase { - status: ToolCallStatus.Streaming; - /** Partial parameters accumulated so far */ - partialInput?: string; - /** Progress message shown while parameters are streaming */ - invocationMessage?: StringOrMarkdown; -} - -/** - * Parameters are complete, or a running tool requires re-confirmation - * (e.g. a mid-execution permission check). - * - * @category Tool Call Types - */ -export interface ToolCallPendingConfirmationState extends ToolCallBase, ToolCallParameterFields { - status: ToolCallStatus.PendingConfirmation; - /** Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) */ - confirmationTitle?: StringOrMarkdown; - /** File edits that this tool call will perform, for preview before confirmation */ - edits?: { items: FileEdit[] }; - /** Whether the agent host allows the client to edit the tool's input parameters before confirming */ - editable?: boolean; - /** - * Options the server offers for this confirmation. When present, the client - * SHOULD render these instead of a plain approve/deny UI. Each option - * belongs to a {@link ConfirmationOptionGroup} so the client can still - * categorise the choices. - */ - options?: ConfirmationOption[]; -} - -/** - * Tool is actively executing. - * - * @category Tool Call Types - */ -export interface ToolCallRunningState extends ToolCallBase, ToolCallParameterFields { - status: ToolCallStatus.Running; - /** How the tool was confirmed for execution */ - confirmed: ToolCallConfirmationReason; - /** The confirmation option the user selected, if confirmation options were provided */ - selectedOption?: ConfirmationOption; - /** - * Partial content produced while the tool is still executing. - * - * For example, a terminal content block lets clients subscribe to live - * output before the tool completes. - */ - content?: ToolResultContent[]; -} - -/** - * Tool finished executing, waiting for client to approve the result. - * - * @category Tool Call Types - */ -export interface ToolCallPendingResultConfirmationState extends ToolCallBase, ToolCallParameterFields, ToolCallResult { - status: ToolCallStatus.PendingResultConfirmation; - /** How the tool was confirmed for execution */ - confirmed: ToolCallConfirmationReason; - /** The confirmation option the user selected, if confirmation options were provided */ - selectedOption?: ConfirmationOption; -} - -/** - * Tool completed successfully or with an error. - * - * @category Tool Call Types - */ -export interface ToolCallCompletedState extends ToolCallBase, ToolCallParameterFields, ToolCallResult { - status: ToolCallStatus.Completed; - /** How the tool was confirmed for execution */ - confirmed: ToolCallConfirmationReason; - /** The confirmation option the user selected, if confirmation options were provided */ - selectedOption?: ConfirmationOption; -} - -/** - * Tool call was cancelled before execution. - * - * @category Tool Call Types - */ -export interface ToolCallCancelledState extends ToolCallBase, ToolCallParameterFields { - status: ToolCallStatus.Cancelled; - /** Why the tool was cancelled */ - reason: ToolCallCancellationReason; - /** Optional message explaining the cancellation */ - reasonMessage?: StringOrMarkdown; - /** What the user suggested doing instead */ - userSuggestion?: Message; - /** The confirmation option the user selected, if confirmation options were provided */ - selectedOption?: ConfirmationOption; -} - -/** - * Discriminated union of all tool call lifecycle states. - * - * See the [state model guide](/guide/state-model.html#tool-call-lifecycle) - * for the full state machine diagram. - * - * @category Tool Call Types - */ -export type ToolCallState = - | ToolCallStreamingState - | ToolCallPendingConfirmationState - | ToolCallRunningState - | ToolCallPendingResultConfirmationState - | ToolCallCompletedState - | ToolCallCancelledState; - - -// ─── Tool Result Content ───────────────────────────────────────────────────── - -/** - * Discriminant for tool result content types. - * - * @category Tool Result Content - */ -export const enum ToolResultContentType { - Text = 'text', - EmbeddedResource = 'embeddedResource', - Resource = 'resource', - FileEdit = 'fileEdit', - Terminal = 'terminal', - Subagent = 'subagent', -} - -/** - * Text content in a tool result. - * - * Mirrors MCP `TextContent`. - * - * @category Tool Result Content - */ -export interface ToolResultTextContent { - type: ToolResultContentType.Text; - /** The text content */ - text: string; -} - -/** - * Base64-encoded binary content embedded in a tool result. - * - * Mirrors MCP `EmbeddedResource` for inline binary data. - * - * @category Tool Result Content - */ -export interface ToolResultEmbeddedResourceContent { - type: ToolResultContentType.EmbeddedResource; - /** Base64-encoded data */ - data: string; - /** Content type (e.g. `"image/png"`, `"application/pdf"`) */ - contentType: string; -} - -/** - * A reference to a resource stored outside the tool result. - * - * Wraps {@link ContentRef} for lazy-loading large results. - * - * @category Tool Result Content - */ -export interface ToolResultResourceContent extends ContentRef { - type: ToolResultContentType.Resource; -} - -/** - * Describes a file modification performed by a tool. - * - * @category Tool Result Content - */ -export interface ToolResultFileEditContent extends FileEdit { - type: ToolResultContentType.FileEdit; -} - -/** - * A reference to a terminal whose output is relevant to this tool result. - * - * Clients can subscribe to the terminal's URI to stream its output in real - * time, providing live feedback while a tool is executing. - * - * @category Tool Result Content - */ -export interface ToolResultTerminalContent { - type: ToolResultContentType.Terminal; - /** Terminal URI (subscribable for full terminal state) */ - resource: URI; - /** Display title for the terminal content */ - title: string; -} - -/** - * A reference to a subagent session spawned by a tool. - * - * Clients can subscribe to the subagent's session URI to stream its - * progress in real time, including inner tool calls and responses. - * - * @category Tool Result Content - */ -export interface ToolResultSubagentContent { - type: ToolResultContentType.Subagent; - /** Subagent session URI (subscribable for full session state) */ - resource: URI; - /** Display title for the subagent */ - title: string; - /** Internal agent name */ - agentName?: string; - /** Human-readable description of the subagent's task */ - description?: string; -} - -/** - * Content block in a tool result. - * - * Mirrors the content blocks in MCP `CallToolResult.content`, plus - * `ToolResultResourceContent` for lazy-loading large results, - * `ToolResultFileEditContent` for file edit diffs, - * `ToolResultTerminalContent` for live terminal output, and - * `ToolResultSubagentContent` for subagent sessions (AHP extensions). - * - * @category Tool Result Content - */ -export type ToolResultContent = - | ToolResultTextContent - | ToolResultEmbeddedResourceContent - | ToolResultResourceContent - | ToolResultFileEditContent - | ToolResultTerminalContent - | ToolResultSubagentContent; - diff --git a/types/channels-session/actions.ts b/types/channels-session/actions.ts index acdfa979..1cc68d84 100644 --- a/types/channels-session/actions.ts +++ b/types/channels-session/actions.ts @@ -5,18 +5,56 @@ */ import { ActionType } from '../common/actions.js'; -import type { ErrorInfo } from '../common/state.js'; +import type { StringOrMarkdown, ErrorInfo, FileEdit, UsageInfo } from '../common/state.js'; import type { + Message, + ResponsePart, + ToolCallResult, + ToolResultContent, ToolDefinition, SessionActiveClient, Customization, McpServerState, + SessionInputAnswer, + SessionInputRequest, + SessionInputResponseKind, + ConfirmationOption, AgentSelection, + ToolCallContributor, } from './state.js'; import type { ModelSelection } from '../channels-root/state.js'; import type { URI } from '../common/state.js'; +import { + ToolCallConfirmationReason, + ToolCallCancellationReason, + PendingMessageKind, +} from './state.js'; import type { Changeset } from '../channels-changeset/state.js'; -import type { ChatSummary } from '../channels-chat/state.js'; + +// ─── Tool Call Action Base ─────────────────────────────────────────────────── + +/** + * Base interface for all tool-call-scoped actions, carrying the common turn + * and tool call identifiers. The owning session URI is identified by the + * enclosing {@link ActionEnvelope}'s `channel` field. + * + * @category Session Actions + */ +interface ToolCallActionBase { + /** Turn identifier */ + turnId: string; + /** Tool call identifier */ + toolCallId: string; + /** + * Additional provider-specific metadata for this tool call. + * + * Clients MAY look for well-known keys here to provide enhanced UI. + * For example, a `ptyTerminal` key with `{ input: string; output: string }` + * indicates the tool operated on a terminal (both `input` and `output` may + * contain escape sequences). + */ + _meta?: Record; +} // ─── Session Actions ───────────────────────────────────────────────────────── @@ -43,71 +81,289 @@ export interface SessionCreationFailedAction { } /** - * A chat was added to this session's catalog. Upsert semantics: if a chat - * with the same `summary.resource` already exists, the existing entry is - * replaced. + * A new message has been sent to the agent, and a new turn starts. * - * Mirrors the root-channel `root/sessionAdded` notification. + * A client is only allowed to send {@link MessageKind.User} messages. * * @category Session Actions * @version 1 + * @clientDispatchable */ -export interface SessionChatAddedAction { - type: ActionType.SessionChatAdded; - /** The full summary of the newly added (or upserted) chat. */ - summary: ChatSummary; +export interface SessionTurnStartedAction { + type: ActionType.SessionTurnStarted; + /** Turn identifier */ + turnId: string; + /** The new message */ + message: Message; + /** If this turn was auto-started from a queued message, the ID of that message */ + queuedMessageId?: string; } /** - * A chat was removed from this session's catalog. No-op when no entry matches. + * Streaming text chunk from the assistant, appended to a specific response part. * - * Mirrors the root-channel `root/sessionRemoved` notification. + * The server MUST first emit a `session/responsePart` to create the target + * part (markdown or reasoning), then use this action to append text to it. * * @category Session Actions * @version 1 */ -export interface SessionChatRemovedAction { - type: ActionType.SessionChatRemoved; - /** The URI of the chat to remove. */ - chat: URI; +export interface SessionDeltaAction { + type: ActionType.SessionDelta; + /** Turn identifier */ + turnId: string; + /** Identifier of the response part to append to */ + partId: string; + /** Text chunk */ + content: string; } /** - * One existing chat's summary fields changed. + * Structured content appended to the response. * - * Partial-update semantics: only fields present in `changes` are written; - * omitted fields are preserved. Identity fields (`resource`) MUST NOT be - * carried in `changes`. No-op when no entry with `chat` exists — clients - * SHOULD then wait for a {@link SessionChatAddedAction | `session/chatAdded`}. + * @category Session Actions + * @version 1 + */ +export interface SessionResponsePartAction { + type: ActionType.SessionResponsePart; + /** Turn identifier */ + turnId: string; + /** Response part (markdown or content ref) */ + part: ResponsePart; +} + +/** + * A tool call begins — parameters are streaming from the LM. * - * Mirrors the root-channel `root/sessionSummaryChanged` notification. + * The server sets {@link ToolCallContributor | `contributor`} to identify + * the origin of the tool. For client-provided tools, the named client is + * responsible for executing the tool once it reaches the `running` state + * and dispatching `session/toolCallComplete`. For MCP-served tools, the + * server executes the call against the named `McpServerCustomization`. * * @category Session Actions * @version 1 */ -export interface SessionChatUpdatedAction { - type: ActionType.SessionChatUpdated; - /** The URI of the chat whose summary changed. */ - chat: URI; +export interface SessionToolCallStartAction extends ToolCallActionBase { + type: ActionType.SessionToolCallStart; + /** Internal tool name (for debugging/logging) */ + toolName: string; + /** Human-readable tool name */ + displayName: string; /** - * Mutable summary fields that changed; omitted fields are unchanged. - * - * Identity fields (`resource`) never change and MUST be omitted by - * senders; receivers SHOULD ignore them if present. + * Reference to the contributor of the tool being called. Absent for + * server-side tools that are not contributed by a client or MCP server. + */ + contributor?: ToolCallContributor; +} + +/** + * Streaming partial parameters for a tool call. + * + * @category Session Actions + * @version 1 + */ +export interface SessionToolCallDeltaAction extends ToolCallActionBase { + type: ActionType.SessionToolCallDelta; + /** Partial parameter content to append */ + content: string; + /** Updated progress message */ + invocationMessage?: StringOrMarkdown; +} + +/** + * Tool call parameters are complete, or a running tool requires re-confirmation. + * + * When dispatched for a `streaming` tool call, transitions to `pending-confirmation` + * or directly to `running` if `confirmed` is set. + * + * When dispatched for a `running` tool call (e.g. mid-execution permission needed), + * transitions back to `pending-confirmation`. The `invocationMessage` and `_meta` + * SHOULD be updated to describe the specific confirmation needed. Clients use the + * standard `session/toolCallConfirmed` flow to approve or deny. + * + * For client-provided tools, the server typically sets `confirmed` to + * `'not-needed'` so the tool transitions directly to `running`, where the + * owning client can begin execution immediately. + * + * @category Session Actions + * @version 1 + */ +export interface SessionToolCallReadyAction extends ToolCallActionBase { + type: ActionType.SessionToolCallReady; + /** Message describing what the tool will do or what confirmation is needed */ + invocationMessage: StringOrMarkdown; + /** Raw tool input */ + toolInput?: string; + /** Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) */ + confirmationTitle?: StringOrMarkdown; + /** File edits that this tool call will perform, for preview before confirmation */ + edits?: { items: FileEdit[] }; + /** Whether the agent host allows the client to edit the tool's input parameters before confirming */ + editable?: boolean; + /** If set, the tool was auto-confirmed and transitions directly to `running` */ + confirmed?: ToolCallConfirmationReason; + /** + * Options the server offers for this confirmation. When present, the client + * SHOULD render these instead of a plain approve/deny UI. Each option + * belongs to a {@link ConfirmationOptionGroup} so the client can still + * categorise the choices. */ - changes: Partial; + options?: ConfirmationOption[]; +} + +/** + * Client approves a pending tool call. The tool transitions to `running`. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionToolCallApprovedAction extends ToolCallActionBase { + type: ActionType.SessionToolCallConfirmed; + /** The tool call was approved */ + approved: true; + /** How the tool was confirmed */ + confirmed: ToolCallConfirmationReason; + /** Edited tool input parameters, if the client modified them before confirming */ + editedToolInput?: string; + /** ID of the selected confirmation option, if the server provided options */ + selectedOptionId?: string; +} + +/** + * Client denies a pending tool call. The tool transitions to `cancelled`. + * + * For client-provided tools, the owning client MUST dispatch this if it does + * not recognize the tool or cannot execute it. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionToolCallDeniedAction extends ToolCallActionBase { + type: ActionType.SessionToolCallConfirmed; + /** The tool call was denied */ + approved: false; + /** Why the tool was cancelled */ + reason: ToolCallCancellationReason.Denied | ToolCallCancellationReason.Skipped; + /** What the user suggested doing instead */ + userSuggestion?: Message; + /** Optional explanation for the denial */ + reasonMessage?: StringOrMarkdown; + /** ID of the selected confirmation option, if the server provided options */ + selectedOptionId?: string; +} + +/** + * Client confirms or denies a pending tool call. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export type SessionToolCallConfirmedAction = + | SessionToolCallApprovedAction + | SessionToolCallDeniedAction; + +/** + * Tool execution finished. Transitions to `completed` or `pending-result-confirmation` + * if `requiresResultConfirmation` is `true`. + * + * For client-provided tools (where `toolClientId` is set on the tool call state), + * the owning client dispatches this action with the execution result. The server + * SHOULD reject this action if the dispatching client does not match `toolClientId`. + * + * Servers waiting on a client tool call MAY time out after a reasonable duration + * if the implementing client disconnects or becomes unresponsive, and dispatch + * this action with `result.success = false` and an appropriate error. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionToolCallCompleteAction extends ToolCallActionBase { + type: ActionType.SessionToolCallComplete; + /** Execution result */ + result: ToolCallResult; + /** If true, the result requires client approval before finalizing */ + requiresResultConfirmation?: boolean; +} + +/** + * Client approves or denies a tool's result. + * + * If `approved` is `false`, the tool transitions to `cancelled` with reason `result-denied`. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionToolCallResultConfirmedAction extends ToolCallActionBase { + type: ActionType.SessionToolCallResultConfirmed; + /** Whether the result was approved */ + approved: boolean; +} + +/** + * Partial content produced while a tool is still executing. + * + * Replaces the `content` array on the running tool call state. Clients can + * use this to display live feedback (e.g. a terminal reference) before the + * tool completes. + * + * For client-provided tools (where `toolClientId` is set on the tool call state), + * the owning client dispatches this action to stream intermediate content while + * executing. The server SHOULD reject this action if the dispatching client does + * not match `toolClientId`. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionToolCallContentChangedAction extends ToolCallActionBase { + type: ActionType.SessionToolCallContentChanged; + /** The current partial content for the running tool call */ + content: ToolResultContent[]; +} + +/** + * Turn finished — the assistant is idle. + * + * @category Session Actions + * @version 1 + */ +export interface SessionTurnCompleteAction { + type: ActionType.SessionTurnComplete; + /** Turn identifier */ + turnId: string; +} + +/** + * Turn was aborted; server stops processing. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionTurnCancelledAction { + type: ActionType.SessionTurnCancelled; + /** Turn identifier */ + turnId: string; } /** - * The default chat input-routing hint for this session changed. + * Error during turn processing. * * @category Session Actions * @version 1 */ -export interface SessionDefaultChatChangedAction { - type: ActionType.SessionDefaultChatChanged; - /** New default chat URI, or `undefined` to clear the hint. */ - defaultChat?: URI; +export interface SessionErrorAction { + type: ActionType.SessionError; + /** Turn identifier */ + turnId: string; + /** Error details */ + error: ErrorInfo; } /** @@ -124,6 +380,39 @@ export interface SessionTitleChangedAction { title: string; } +/** + * Token usage report for a turn. + * + * @category Session Actions + * @version 1 + */ +export interface SessionUsageAction { + type: ActionType.SessionUsage; + /** Turn identifier */ + turnId: string; + /** Token usage data */ + usage: UsageInfo; +} + +/** + * Reasoning/thinking text from the model, appended to a specific reasoning response part. + * + * The server MUST first emit a `session/responsePart` to create the target + * reasoning part, then use this action to append text to it. + * + * @category Session Actions + * @version 1 + */ +export interface SessionReasoningAction { + type: ActionType.SessionReasoning; + /** Turn identifier */ + turnId: string; + /** Identifier of the reasoning response part to append to */ + partId: string; + /** Reasoning text chunk */ + content: string; +} + /** * Model changed for this session. * @@ -422,3 +711,148 @@ export interface SessionMetaChangedAction { /** New `_meta` payload, or `undefined` to clear it */ _meta: Record | undefined; } + +// ─── Truncation ────────────────────────────────────────────────────────────── + +/** + * Truncates a session's history. If `turnId` is provided, all turns after that + * turn are removed and the specified turn is kept. If `turnId` is omitted, all + * turns are removed. + * + * If there is an active turn it is silently dropped and the session status + * returns to `idle`. + * + * Common use-case: truncate old data then dispatch a new + * `session/turnStarted` with an edited message. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionTruncatedAction { + type: ActionType.SessionTruncated; + /** Keep turns up to and including this turn. Omit to clear all turns. */ + turnId?: string; +} + +// ─── Pending Message Actions ───────────────────────────────────────────────── + +/** + * A pending message was set (upsert semantics: creates or replaces). + * + * For steering messages, this always replaces the single steering message. + * For queued messages, if a message with the given `id` already exists it is + * updated in place; otherwise it is appended to the queue. If the session is + * idle when a queued message is set, the server SHOULD immediately consume it + * and start a new turn. + * + * A client is only allowed to send {@link MessageKind.User} messages. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionPendingMessageSetAction { + type: ActionType.SessionPendingMessageSet; + /** Whether this is a steering or queued message */ + kind: PendingMessageKind; + /** Unique identifier for this pending message */ + id: string; + /** The message content */ + message: Message; +} + +/** + * A pending message was removed (steering or queued). + * + * Dispatched by clients to cancel a pending message, or by the server when + * it consumes a message (e.g. starting a turn from a queued message or + * injecting a steering message into the current turn). + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionPendingMessageRemovedAction { + type: ActionType.SessionPendingMessageRemoved; + /** Whether this is a steering or queued message */ + kind: PendingMessageKind; + /** Identifier of the pending message to remove */ + id: string; +} + +/** + * Reorder the queued messages. + * + * The `order` array contains the IDs of queued messages in their new + * desired order. IDs not present in the current queue are ignored. + * Queued messages whose IDs are absent from `order` are appended at + * the end in their original relative order (so a client with a stale + * view of the queue never silently drops messages). + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionQueuedMessagesReorderedAction { + type: ActionType.SessionQueuedMessagesReordered; + /** Queued message IDs in the desired order */ + order: string[]; +} + +// ─── Session Input Actions ────────────────────────────────────────────────── + +/** + * A session requested input from the user. + * + * Full-request upsert semantics: the `request` replaces any existing request + * with the same `id`, or is appended if it is new. Answer drafts are preserved + * unless `request.answers` is provided. + * + * @category Session Actions + * @version 1 + */ +export interface SessionInputRequestedAction { + type: ActionType.SessionInputRequested; + /** Input request to create or replace */ + request: SessionInputRequest; +} + +/** + * A client updated, submitted, skipped, or removed a single in-progress answer. + * + * Dispatching with `answer: undefined` removes that question's answer draft. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionInputAnswerChangedAction { + type: ActionType.SessionInputAnswerChanged; + /** Input request identifier */ + requestId: string; + /** Question identifier within the input request */ + questionId: string; + /** Updated answer, or `undefined` to clear an answer draft */ + answer?: SessionInputAnswer; +} + +/** + * A client accepted, declined, or cancelled a session input request. + * + * If accepted, the server uses `answers` (when provided) plus the request's + * synced answer state to resume the blocked operation. + * + * @category Session Actions + * @version 1 + * @clientDispatchable + */ +export interface SessionInputCompletedAction { + type: ActionType.SessionInputCompleted; + /** Input request identifier */ + requestId: string; + /** Completion outcome */ + response: SessionInputResponseKind; + /** Optional final answer replacement, keyed by question ID */ + answers?: Record; +} diff --git a/types/channels-session/commands.ts b/types/channels-session/commands.ts index 560d9b56..6731efc7 100644 --- a/types/channels-session/commands.ts +++ b/types/channels-session/commands.ts @@ -9,13 +9,11 @@ import type { URI } from '../common/state.js'; import type { BaseParams } from '../common/commands.js'; import type { ModelSelection } from '../channels-root/state.js'; import type { + Turn, SessionActiveClient, + MessageAttachment, AgentSelection, } from './state.js'; -import type { - Turn, - MessageAttachment, -} from '../channels-chat/state.js'; // ─── createSession ─────────────────────────────────────────────────────────── @@ -118,7 +116,7 @@ export interface DisposeSessionParams extends BaseParams {} // ─── fetchTurns ────────────────────────────────────────────────────────────── /** - * Fetches historical turns for a chat. Used for lazy loading of conversation + * Fetches historical turns for a session. Used for lazy loading of conversation * history. * * @category Commands @@ -130,7 +128,7 @@ export interface DisposeSessionParams extends BaseParams {} * ```jsonc * // Client → Server (fetch the 20 most recent turns) * { "jsonrpc": "2.0", "id": 8, "method": "fetchTurns", - * "params": { "channel": "ahp-chat:/", "limit": 20 } } + * "params": { "channel": "ahp-session:/", "limit": 20 } } * * // Server → Client * { "jsonrpc": "2.0", "id": 8, "result": { @@ -140,11 +138,11 @@ export interface DisposeSessionParams extends BaseParams {} * * // Client → Server (fetch 20 turns before t1) * { "jsonrpc": "2.0", "id": 9, "method": "fetchTurns", - * "params": { "channel": "ahp-chat:/", "before": "t1", "limit": 20 } } + * "params": { "channel": "ahp-session:/", "before": "t1", "limit": 20 } } * ``` */ export interface FetchTurnsParams extends BaseParams { - /** Chat URI */ + /** Session URI */ channel: URI; /** Turn ID to fetch before (exclusive). Omit to fetch from the most recent turn. */ before?: string; @@ -197,7 +195,7 @@ export const enum CompletionItemKind { * // User has typed "look at @foo" and the cursor is just after "@foo". * // Client → Server * { "jsonrpc": "2.0", "id": 12, "method": "completions", - * "params": { "kind": "userMessage", "channel": "ahp-chat:/", + * "params": { "kind": "userMessage", "channel": "ahp-session:/", * "text": "look at @foo", "offset": 12 } } * * // Server → Client @@ -221,7 +219,7 @@ export const enum CompletionItemKind { export interface CompletionsParams extends BaseParams { /** What kind of completion is being requested. */ kind: CompletionItemKind; - /** The chat URI the completion is being requested for. */ + /** The session URI the completion is being requested for. */ channel: URI; /** * The complete text of the input being completed (e.g. the full user diff --git a/types/channels-session/reducer.ts b/types/channels-session/reducer.ts index 8e902cb0..49b04d01 100644 --- a/types/channels-session/reducer.ts +++ b/types/channels-session/reducer.ts @@ -1,17 +1,32 @@ /** - * Session Channel Reducer — Pure reducer for `SessionState`. + * Session Channel Reducer — Pure reducer for `SessionState`, including all + * session-internal helpers (turn lifecycle, tool call transitions, pending + * messages, input requests). * * @module channels-session/reducer */ import { ActionType } from '../common/actions.js'; import type { + SessionInputRequest, SessionState, + ToolCallState, + ResponsePart, + ToolCallResponsePart, + Turn, + PendingMessage, + ConfirmationOption, McpServerCustomization, } from './state.js'; import { SessionLifecycle, SessionStatus, + TurnState, + ToolCallStatus, + ToolCallConfirmationReason, + ToolCallCancellationReason, + ResponsePartKind, + PendingMessageKind, CustomizationType, } from './state.js'; import type { SessionAction } from '../action-origin.generated.js'; @@ -19,11 +34,235 @@ import { softAssertNever } from '../common/reducer-helpers.js'; // ─── Helpers ───────────────────────────────────────────────────────────────── +/** Extracts the common base fields shared by all tool call lifecycle states. */ +function tcBase(tc: ToolCallState) { + return { + toolCallId: tc.toolCallId, + toolName: tc.toolName, + displayName: tc.displayName, + contributor: tc.contributor, + _meta: tc._meta, + }; +} + +function tcBaseWithMeta(tc: ToolCallState, meta: Record | undefined) { + return { + ...tcBase(tc), + _meta: meta ?? tc._meta, + }; +} + +/** Resolves a selected option from the confirmation options array by ID. */ +function resolveSelectedOption(options: ConfirmationOption[] | undefined, id: string | undefined): ConfirmationOption | undefined { + if (!id || !options) { + return undefined; + } + return options.find(o => o.id === id); +} + +/** Returns `true` if the active turn has any tool call awaiting user confirmation. */ +function hasPendingToolCallConfirmation(state: SessionState): boolean { + if (!state.activeTurn) { + return false; + } + return state.activeTurn.responseParts.some(part => + part.kind === ResponsePartKind.ToolCall + && (part.toolCall.status === ToolCallStatus.PendingConfirmation + || part.toolCall.status === ToolCallStatus.PendingResultConfirmation), + ); +} + +/** Bitmask covering the mutually-exclusive activity bits (bits 0–4). */ +const STATUS_ACTIVITY_MASK = (1 << 5) - 1; + /** Sets or clears a metadata flag on a status value. */ function withStatusFlag(status: SessionStatus, flag: SessionStatus, set: boolean): SessionStatus { return set ? status | flag : status & ~flag; } +/** Derives the summary status from live session work, preserving orthogonal flags. */ +function summaryStatus(state: SessionState, terminalStatus?: SessionStatus.Error): SessionStatus { + let activity: SessionStatus; + if (terminalStatus) { + activity = terminalStatus; + } else if ((state.inputRequests?.length ?? 0) > 0 || hasPendingToolCallConfirmation(state)) { + activity = SessionStatus.InputNeeded; + } else if (state.activeTurn) { + activity = SessionStatus.InProgress; + } else { + activity = SessionStatus.Idle; + } + + return state.summary.status & ~STATUS_ACTIVITY_MASK | activity; +} + +/** + * Returns a state with `summary.status` recomputed. Use this after reducers + * that change data which feeds into {@link summaryStatus} (e.g. tool call + * lifecycle transitions that may enter or leave a pending-confirmation state). + */ +function refreshSummaryStatus(state: SessionState): SessionState { + const status = summaryStatus(state); + if (status === state.summary.status) { + return state; + } + return { ...state, summary: { ...state.summary, status } }; +} + +/** + * Ends the active turn, finalizing it into a completed turn record. + * + * Tool call parts with non-terminal states are forced to cancelled. + * Pending permissions are stripped from tool call parts. + */ +function endTurn( + state: SessionState, + turnId: string, + turnState: TurnState, + terminalStatus?: SessionStatus.Error, + error?: { errorType: string; message: string; stack?: string }, +): SessionState { + if (!state.activeTurn || state.activeTurn.id !== turnId) { + return state; + } + const active = state.activeTurn; + + const responseParts: ResponsePart[] = active.responseParts.map(part => { + if (part.kind !== ResponsePartKind.ToolCall) { + return part; + } + const tc = part.toolCall; + if (tc.status === ToolCallStatus.Completed || tc.status === ToolCallStatus.Cancelled) { + return part; + } + // Force non-terminal tool calls into cancelled state + return { + kind: ResponsePartKind.ToolCall, + toolCall: { + status: ToolCallStatus.Cancelled as const, + ...tcBase(tc), + invocationMessage: tc.status === ToolCallStatus.Streaming ? (tc.invocationMessage ?? '') : tc.invocationMessage, + toolInput: tc.status === ToolCallStatus.Streaming ? undefined : tc.toolInput, + reason: ToolCallCancellationReason.Skipped, + }, + }; + }); + + const turn: Turn = { + id: active.id, + message: active.message, + responseParts, + usage: active.usage, + state: turnState, + error, + }; + + const next: SessionState = { + ...state, + turns: [...state.turns, turn], + activeTurn: undefined, + summary: { ...state.summary, modifiedAt: Date.now() }, + }; + delete next.inputRequests; + return { + ...next, + summary: { ...next.summary, status: summaryStatus(next, terminalStatus) }, + }; +} + +function upsertInputRequest(state: SessionState, request: SessionInputRequest): SessionState { + const existing = state.inputRequests ?? []; + const idx = existing.findIndex(r => r.id === request.id); + const inputRequests = [...existing]; + if (idx >= 0) { + const answers = request.answers ?? inputRequests[idx].answers; + inputRequests[idx] = { ...request, answers }; + } else { + inputRequests.push(request); + } + const next = { ...state, inputRequests }; + return { ...next, summary: { ...next.summary, status: withStatusFlag(summaryStatus(next), SessionStatus.IsRead, false), modifiedAt: Date.now() } }; +} + +/** + * Immutably updates the tool call inside a `ToolCall` response part in the + * active turn's `responseParts` array. Returns `state` unchanged if the + * active turn or tool call doesn't match. + */ +function updateToolCallInParts( + state: SessionState, + turnId: string, + toolCallId: string, + updater: (tc: ToolCallState) => ToolCallState, +): SessionState { + const activeTurn = state.activeTurn; + if (!activeTurn || activeTurn.id !== turnId) { + return state; + } + + let found = false; + const responseParts = activeTurn.responseParts.map(part => { + if (part.kind === ResponsePartKind.ToolCall && part.toolCall.toolCallId === toolCallId) { + const updated = updater(part.toolCall); + if (updated === part.toolCall) { + return part; + } + found = true; + return { ...part, toolCall: updated }; + } + return part; + }); + + if (!found) { + return state; + } + + return { + ...state, + activeTurn: { ...activeTurn, responseParts }, + }; +} + +/** + * Immutably updates a response part by `partId` in the active turn. + * For markdown/reasoning parts, matches on `id`. For tool call parts, + * matches on `toolCall.toolCallId`. + */ +function updateResponsePart( + state: SessionState, + turnId: string, + partId: string, + updater: (part: ResponsePart) => ResponsePart, +): SessionState { + const activeTurn = state.activeTurn; + if (!activeTurn || activeTurn.id !== turnId) { + return state; + } + + let found = false; + const responseParts = activeTurn.responseParts.map(part => { + if (!found) { + const id = part.kind === ResponsePartKind.ToolCall + ? part.toolCall.toolCallId + : 'id' in part ? part.id : undefined; + if (id === partId) { + found = true; + return updater(part); + } + } + return part; + }); + + if (!found) { + return state; + } + + return { + ...state, + activeTurn: { ...activeTurn, responseParts }, + }; +} + // ─── Session Reducer ───────────────────────────────────────────────────────── /** @@ -51,46 +290,238 @@ export function sessionReducer(state: SessionState, action: SessionAction, log?: creationError: action.error, }; - case ActionType.SessionChatAdded: { - const list = state.chats; - const idx = list.findIndex(c => c.resource === action.summary.resource); - if (idx < 0) { - return { ...state, chats: [...list, action.summary] }; + // ── Turn Lifecycle ──────────────────────────────────────────────────── + + case ActionType.SessionTurnStarted: { + let next: SessionState = { + ...state, + activeTurn: { + id: action.turnId, + message: action.message, + responseParts: [], + usage: undefined, + }, + }; + next = { + ...next, + summary: { ...next.summary, status: withStatusFlag(summaryStatus(next), SessionStatus.IsRead, false), modifiedAt: Date.now() }, + }; + + // If this turn was auto-started from a pending message, remove it + if (action.queuedMessageId) { + if (next.steeringMessage?.id === action.queuedMessageId) { + next = { ...next, steeringMessage: undefined }; + } + if (next.queuedMessages) { + const filtered = next.queuedMessages.filter(m => m.id !== action.queuedMessageId); + next = { ...next, queuedMessages: filtered.length > 0 ? filtered : undefined }; + } } - const updated = list.slice(); - updated[idx] = action.summary; - return { ...state, chats: updated }; + + return next; } - case ActionType.SessionChatRemoved: { - const list = state.chats; - const idx = list.findIndex(c => c.resource === action.chat); - if (idx < 0) { + case ActionType.SessionDelta: + return updateResponsePart(state, action.turnId, action.partId, part => { + if (part.kind === ResponsePartKind.Markdown) { + return { ...part, content: part.content + action.content }; + } + return part; + }); + + case ActionType.SessionResponsePart: + if (!state.activeTurn || state.activeTurn.id !== action.turnId) { return state; } - const updated = list.slice(); - updated.splice(idx, 1); - const next: SessionState = { ...state, chats: updated }; - if (state.defaultChat === action.chat) { - delete next.defaultChat; - } - return next; - } + return { + ...state, + activeTurn: { + ...state.activeTurn, + responseParts: [...state.activeTurn.responseParts, action.part], + }, + }; - case ActionType.SessionChatUpdated: { - const list = state.chats; - const idx = list.findIndex(c => c.resource === action.chat); - if (idx < 0) { + case ActionType.SessionTurnComplete: + return endTurn(state, action.turnId, TurnState.Complete); + + case ActionType.SessionTurnCancelled: + return endTurn(state, action.turnId, TurnState.Cancelled); + + case ActionType.SessionError: + return endTurn(state, action.turnId, TurnState.Error, SessionStatus.Error, action.error); + + // ── Tool Call State Machine ─────────────────────────────────────────── + + case ActionType.SessionToolCallStart: + if (!state.activeTurn || state.activeTurn.id !== action.turnId) { return state; } - const { resource: _ignored, ...changes } = action.changes; - const updated = list.slice(); - updated[idx] = { ...list[idx], ...changes }; - return { ...state, chats: updated }; - } + return { + ...state, + activeTurn: { + ...state.activeTurn, + responseParts: [ + ...state.activeTurn.responseParts, + { + kind: ResponsePartKind.ToolCall, + toolCall: { + toolCallId: action.toolCallId, + toolName: action.toolName, + displayName: action.displayName, + contributor: action.contributor, + _meta: action._meta, + status: ToolCallStatus.Streaming, + }, + } satisfies ToolCallResponsePart, + ], + }, + }; + + case ActionType.SessionToolCallDelta: + return updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { + if (tc.status !== ToolCallStatus.Streaming) { + return tc; + } + return { + ...tc, + ...(action._meta !== undefined ? { _meta: action._meta } : {}), + partialInput: (tc.partialInput ?? '') + action.content, + invocationMessage: action.invocationMessage ?? tc.invocationMessage, + }; + }); + + case ActionType.SessionToolCallReady: + return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { + if (tc.status !== ToolCallStatus.Streaming && tc.status !== ToolCallStatus.Running) { + return tc; + } + const base = tcBaseWithMeta(tc, action._meta); + if (action.confirmed) { + return { + status: ToolCallStatus.Running, + ...base, + invocationMessage: action.invocationMessage, + toolInput: action.toolInput, + confirmed: action.confirmed, + }; + } + return { + status: ToolCallStatus.PendingConfirmation, + ...base, + invocationMessage: action.invocationMessage, + toolInput: action.toolInput, + confirmationTitle: action.confirmationTitle, + edits: action.edits, + editable: action.editable, + ...(action.options ? { options: action.options } : {}), + }; + })); + + case ActionType.SessionToolCallConfirmed: + return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { + if (tc.status !== ToolCallStatus.PendingConfirmation) { + return tc; + } + const base = tcBaseWithMeta(tc, action._meta); + const selectedOption = resolveSelectedOption(tc.options, action.selectedOptionId); + if (action.approved) { + return { + status: ToolCallStatus.Running, + ...base, + invocationMessage: tc.invocationMessage, + toolInput: action.editedToolInput ?? tc.toolInput, + confirmed: action.confirmed, + ...(selectedOption ? { selectedOption } : {}), + }; + } + return { + status: ToolCallStatus.Cancelled, + ...base, + invocationMessage: tc.invocationMessage, + toolInput: tc.toolInput, + reason: action.reason, + reasonMessage: action.reasonMessage, + userSuggestion: action.userSuggestion, + ...(selectedOption ? { selectedOption } : {}), + }; + })); + + case ActionType.SessionToolCallComplete: + return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { + if (tc.status !== ToolCallStatus.Running && tc.status !== ToolCallStatus.PendingConfirmation) { + return tc; + } + const base = tcBaseWithMeta(tc, action._meta); + const confirmed = tc.status === ToolCallStatus.Running + ? tc.confirmed + : ToolCallConfirmationReason.NotNeeded; + const selectedOption = tc.status === ToolCallStatus.Running + ? tc.selectedOption + : undefined; + if (action.requiresResultConfirmation) { + return { + status: ToolCallStatus.PendingResultConfirmation, + ...base, + invocationMessage: tc.invocationMessage, + toolInput: tc.toolInput, + confirmed, + ...(selectedOption ? { selectedOption } : {}), + ...action.result, + }; + } + return { + status: ToolCallStatus.Completed, + ...base, + invocationMessage: tc.invocationMessage, + toolInput: tc.toolInput, + confirmed, + ...(selectedOption ? { selectedOption } : {}), + ...action.result, + }; + })); + + case ActionType.SessionToolCallResultConfirmed: + return refreshSummaryStatus(updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { + if (tc.status !== ToolCallStatus.PendingResultConfirmation) { + return tc; + } + const base = tcBaseWithMeta(tc, action._meta); + if (action.approved) { + return { + status: ToolCallStatus.Completed, + ...base, + invocationMessage: tc.invocationMessage, + toolInput: tc.toolInput, + confirmed: tc.confirmed, + ...(tc.selectedOption ? { selectedOption: tc.selectedOption } : {}), + success: tc.success, + pastTenseMessage: tc.pastTenseMessage, + content: tc.content, + structuredContent: tc.structuredContent, + error: tc.error, + }; + } + return { + status: ToolCallStatus.Cancelled, + ...base, + invocationMessage: tc.invocationMessage, + toolInput: tc.toolInput, + reason: ToolCallCancellationReason.ResultDenied, + ...(tc.selectedOption ? { selectedOption: tc.selectedOption } : {}), + }; + })); - case ActionType.SessionDefaultChatChanged: - return { ...state, defaultChat: action.defaultChat }; + case ActionType.SessionToolCallContentChanged: + return updateToolCallInParts(state, action.turnId, action.toolCallId, tc => { + if (tc.status !== ToolCallStatus.Running) { + return tc; + } + return { + ...tc, + ...(action._meta !== undefined ? { _meta: action._meta } : {}), + content: action.content, + }; + }); // ── Metadata ────────────────────────────────────────────────────────── @@ -100,6 +531,23 @@ export function sessionReducer(state: SessionState, action: SessionAction, log?: summary: { ...state.summary, title: action.title, modifiedAt: Date.now() }, }; + case ActionType.SessionUsage: + if (!state.activeTurn || state.activeTurn.id !== action.turnId) { + return state; + } + return { + ...state, + activeTurn: { ...state.activeTurn, usage: action.usage }, + }; + + case ActionType.SessionReasoning: + return updateResponsePart(state, action.turnId, action.partId, part => { + if (part.kind === ResponsePartKind.Reasoning) { + return { ...part, content: part.content + action.content }; + } + return part; + }); + case ActionType.SessionModelChanged: return { ...state, @@ -292,6 +740,141 @@ export function sessionReducer(state: SessionState, action: SessionAction, log?: return { ...state, customizations: updated }; } + // ── Truncation ──────────────────────────────────────────────────────── + + case ActionType.SessionTruncated: { + let turns: typeof state.turns; + if (action.turnId === undefined) { + turns = []; + } else { + const idx = state.turns.findIndex(t => t.id === action.turnId); + if (idx < 0) { + return state; + } + turns = state.turns.slice(0, idx + 1); + } + const next: SessionState = { + ...state, + turns, + activeTurn: undefined, + summary: { ...state.summary, modifiedAt: Date.now() }, + }; + delete next.inputRequests; + return { + ...next, + summary: { ...next.summary, status: summaryStatus(next) }, + }; + } + + // ── Session Input Requests ───────────────────────────────────────────── + + case ActionType.SessionInputRequested: + return upsertInputRequest(state, action.request); + + case ActionType.SessionInputAnswerChanged: { + const existing = state.inputRequests; + const idx = existing?.findIndex(request => request.id === action.requestId) ?? -1; + if (!existing || idx < 0) { + return state; + } + const request = existing[idx]; + const answers = { ...(request.answers ?? {}) }; + if (action.answer === undefined) { + delete answers[action.questionId]; + } else { + answers[action.questionId] = action.answer; + } + const updated = [...existing]; + updated[idx] = { + ...request, + answers: Object.keys(answers).length > 0 ? answers : undefined, + }; + return { + ...state, + inputRequests: updated, + summary: { ...state.summary, modifiedAt: Date.now() }, + }; + } + + case ActionType.SessionInputCompleted: { + const existing = state.inputRequests; + if (!existing?.some(request => request.id === action.requestId)) { + return state; + } + const inputRequests = existing.filter(request => request.id !== action.requestId); + const next: SessionState = { + ...state, + }; + if (inputRequests.length > 0) { + next.inputRequests = inputRequests; + } else { + delete next.inputRequests; + } + return { + ...next, + summary: { ...next.summary, status: summaryStatus(next), modifiedAt: Date.now() }, + }; + } + + // ── Pending Messages ────────────────────────────────────────────────── + + case ActionType.SessionPendingMessageSet: { + const entry: PendingMessage = { id: action.id, message: action.message }; + if (action.kind === PendingMessageKind.Steering) { + return { ...state, steeringMessage: entry }; + } + const existing = state.queuedMessages ?? []; + const idx = existing.findIndex(m => m.id === action.id); + if (idx >= 0) { + const updated = [...existing]; + updated[idx] = entry; + return { ...state, queuedMessages: updated }; + } + return { ...state, queuedMessages: [...existing, entry] }; + } + + case ActionType.SessionPendingMessageRemoved: { + if (action.kind === PendingMessageKind.Steering) { + if (!state.steeringMessage || state.steeringMessage.id !== action.id) { + return state; + } + return { ...state, steeringMessage: undefined }; + } + const existing = state.queuedMessages; + if (!existing) { + return state; + } + const filtered = existing.filter(m => m.id !== action.id); + return filtered.length === existing.length + ? state + : { ...state, queuedMessages: filtered.length > 0 ? filtered : undefined }; + } + + case ActionType.SessionQueuedMessagesReordered: { + const existing = state.queuedMessages; + if (!existing) { + return state; + } + const byId = new Map(existing.map(m => [m.id, m])); + const ordered = new Set(); + const reordered = action.order + .filter(id => { + if (byId.has(id) && !ordered.has(id)) { + ordered.add(id); + return true; + } + return false; + }) + .map(id => byId.get(id)!); + // Append any messages not mentioned in order, preserving original order + for (const m of existing) { + if (!ordered.has(m.id)) { + reordered.push(m); + } + } + return { ...state, queuedMessages: reordered }; + } + default: softAssertNever(action, log); return state; diff --git a/types/channels-session/state.ts b/types/channels-session/state.ts index 48a5ca6f..dd38b953 100644 --- a/types/channels-session/state.ts +++ b/types/channels-session/state.ts @@ -1,22 +1,57 @@ /** - * Session State Types — Per-session coordination state exposed on `ahp-session:` channels. + * Session State Types — Per-session state, turns, messages, response parts, + * tool calls, and elicitation/input requests exposed on `ahp-session:` channels. * * @module channels-session/state */ import type { Changeset } from '../channels-changeset/state.js'; import type { AnnotationsSummary } from '../channels-annotations/state.js'; -import type { ChatSummary } from '../channels-chat/state.js'; import type { ModelSelection } from '../channels-root/state.js'; import type { ConfigPropertySchema, + ContentRef, ErrorInfo, + FileEdit, Icon, ProtectedResourceMetadata, + StringOrMarkdown, TextRange, + TextSelection, URI, + UsageInfo, } from '../common/state.js'; +// ─── Pending Message Types ─────────────────────────────────────────────────── + +/** + * Discriminant for pending message kinds. + * + * @category Pending Message Types + */ +export const enum PendingMessageKind { + /** Injected into the current turn at a convenient point */ + Steering = 'steering', + /** Sent automatically as a new turn after the current turn finishes */ + Queued = 'queued', +} + +/** + * A message queued for future delivery to the agent. + * + * Steering messages are injected into the current turn mid-flight. + * Queued messages are automatically started as new turns after the + * current turn naturally finishes. + * + * @category Pending Message Types + */ +export interface PendingMessage { + /** Unique identifier for this pending message */ + id: string; + /** The message that will start the next turn */ + message: Message; +} + // ─── Session State ─────────────────────────────────────────────────────────── /** @@ -70,15 +105,16 @@ export interface SessionState { serverTools?: ToolDefinition[]; /** The client currently providing tools and interactive capabilities to this session */ activeClient?: SessionActiveClient; - /** Catalog of chats in this session. */ - chats: ChatSummary[]; - /** - * The chat that receives input when the user addresses the session without - * selecting a specific chat. This is a UI routing hint, not a hierarchy - * marker — chats remain equal peers at the protocol level. Hosts MAY change - * this over the session's lifetime. - */ - defaultChat?: URI; + /** Completed turns */ + turns: Turn[]; + /** Currently in-progress turn */ + activeTurn?: ActiveTurn; + /** Message to inject into the current turn at a convenient point */ + steeringMessage?: PendingMessage; + /** Messages to send automatically as new turns after the current turn finishes */ + queuedMessages?: PendingMessage[]; + /** Requests for user input that are currently blocking or informing session progress */ + inputRequests?: SessionInputRequest[]; /** Session configuration schema and current values */ config?: SessionConfigState; /** @@ -160,40 +196,6 @@ export interface ProjectInfo { } /** - * Lightweight catalog entry summarizing one session. Surfaced via - * {@link RootChannelCommands.listSessions | `root/listSessions`} and - * `root/sessionAdded`/`root/sessionSummaryChanged` notifications. - * - * **Aggregation across chats.** Once a session contains more than one chat, - * several `SessionSummary` fields are derived from the underlying - * {@link SessionState.chats | chat catalog}. Producers SHOULD follow these - * rules so clients that only consume the session summary (e.g. a session - * list) still see meaningful state: - * - * - `status`: take the activity bits (`Idle` / `InProgress` / `InputNeeded` / - * `Error` — bits 0–4) from the - * {@link SessionState.defaultChat | default chat} when present, else from - * the most recently modified chat. **Promote** `InputNeeded` whenever any - * chat in the session needs input, and **promote** `Error` whenever any - * chat is in an error state — both override the default-chat bits. The - * orthogonal flag bits (`IsRead`, `IsArchived`) remain session-scoped. - * - `activity`: mirror the activity string of the default chat, or of the - * chat currently driving the promoted status bits when a non-default chat - * wins (e.g. the chat that raised `InputNeeded`). - * - `modifiedAt`: the max of all chats' `modifiedAt`. - * - `model` / `agent`: the session-level selection. Per-chat overrides are - * surfaced on individual {@link ChatSummary} entries, not aggregated up. - * - `workingDirectory`: the session-level **default**. Individual chats MAY - * override via {@link ChatSummary.workingDirectory}; aggregating these up - * is meaningless and SHOULD NOT be attempted. - * - `changes`: optional roll-up across all chats. Producers MAY sum the - * per-chat changeset stats or report the most expensive chat's stats — - * whichever is cheaper for the host to compute. - * - * Sessions with a single chat trivially satisfy all of the above (the chat's - * values pass through unchanged). The rules only matter once a session - * carries multiple chats. - * * @category Session State */ export interface SessionSummary { @@ -222,12 +224,7 @@ export interface SessionSummary { * — the session uses the provider's default behavior. */ agent?: AgentSelection; - /** - * The default working directory URI for this session. Individual chats - * MAY override via {@link ChatSummary.workingDirectory | their own - * `workingDirectory`}; this field acts as the fallback for any chat that - * does not. - */ + /** The working directory URI for this session */ workingDirectory?: URI; /** * Aggregate summary of file changes associated with this session. Servers @@ -336,6 +333,875 @@ export interface SessionConfigState { values: Record; } +// ─── Session Input Types ──────────────────────────────────────────────────── + +/** + * How a client completed an input request. + * + * @category Session Input Types + */ +export const enum SessionInputResponseKind { + Accept = 'accept', + Decline = 'decline', + Cancel = 'cancel', +} + +/** + * Question/input control kind. + * + * @category Session Input Types + */ +export const enum SessionInputQuestionKind { + Text = 'text', + Number = 'number', + Integer = 'integer', + Boolean = 'boolean', + SingleSelect = 'single-select', + MultiSelect = 'multi-select', +} + +/** + * A choice in a select-style question. + * + * @category Session Input Types + */ +export interface SessionInputOption { + /** Stable option identifier; for MCP enum values this is the enum string */ + id: string; + /** Display label */ + label: string; + /** Optional secondary text */ + description?: string; + /** Whether this option is the recommended/default choice */ + recommended?: boolean; +} + +interface SessionInputQuestionBase { + /** Stable question identifier used as the key in `answers` */ + id: string; + /** Short display title */ + title?: string; + /** Prompt shown to the user */ + message: string; + /** Whether the user must answer this question to accept the request */ + required?: boolean; +} + +/** Text question within a session input request. */ +export interface SessionInputTextQuestion extends SessionInputQuestionBase { + kind: SessionInputQuestionKind.Text; + /** Format hint for text questions, such as `email`, `uri`, `date`, or `date-time` */ + format?: string; + /** Minimum string length */ + min?: number; + /** Maximum string length */ + max?: number; + /** Default text */ + defaultValue?: string; +} + +/** Numeric question within a session input request. */ +export interface SessionInputNumberQuestion extends SessionInputQuestionBase { + kind: SessionInputQuestionKind.Number | SessionInputQuestionKind.Integer; + /** + * Minimum value + * @format float + */ + min?: number; + /** + * Maximum value + * @format float + */ + max?: number; + /** + * Default numeric value + * @format float + */ + defaultValue?: number; +} + +/** Boolean question within a session input request. */ +export interface SessionInputBooleanQuestion extends SessionInputQuestionBase { + kind: SessionInputQuestionKind.Boolean; + /** Default boolean value */ + defaultValue?: boolean; +} + +/** Single-select question within a session input request. */ +export interface SessionInputSingleSelectQuestion extends SessionInputQuestionBase { + kind: SessionInputQuestionKind.SingleSelect; + /** Options the user may select from */ + options: SessionInputOption[]; + /** Whether the user may enter text instead of selecting an option */ + allowFreeformInput?: boolean; +} + +/** Multi-select question within a session input request. */ +export interface SessionInputMultiSelectQuestion extends SessionInputQuestionBase { + kind: SessionInputQuestionKind.MultiSelect; + /** Options the user may select from */ + options: SessionInputOption[]; + /** Whether the user may enter text in addition to selecting options */ + allowFreeformInput?: boolean; + /** Minimum selected item count */ + min?: number; + /** Maximum selected item count */ + max?: number; +} + +/** + * One question within a session input request. + * + * @category Session Input Types + */ +export type SessionInputQuestion = SessionInputTextQuestion + | SessionInputNumberQuestion + | SessionInputBooleanQuestion + | SessionInputSingleSelectQuestion + | SessionInputMultiSelectQuestion; + +/** + * A live request for user input. + * + * The server creates or replaces requests with `session/inputRequested`. + * Clients sync drafts with `session/inputAnswerChanged` and complete requests + * with `session/inputCompleted`. + * + * @category Session Input Types + */ +export interface SessionInputRequest { + /** Stable request identifier */ + id: string; + /** Display message for the request as a whole */ + message?: string; + /** URL the user should review or open, for URL-style elicitations */ + url?: URI; + /** Ordered questions to ask the user */ + questions?: SessionInputQuestion[]; + /** Current draft or submitted answers, keyed by question ID */ + answers?: Record; +} + +/** + * Answer value kind. + * + * @category Session Input Types + */ +export const enum SessionInputAnswerValueKind { + Text = 'text', + Number = 'number', + Boolean = 'boolean', + Selected = 'selected', + SelectedMany = 'selected-many', +} + +/** + * Value captured for one answer. + * + * @category Session Input Types + */ +export interface SessionInputTextAnswerValue { + kind: SessionInputAnswerValueKind.Text; + value: string; +} + +export interface SessionInputNumberAnswerValue { + kind: SessionInputAnswerValueKind.Number; + /** @format float */ + value: number; +} + +export interface SessionInputBooleanAnswerValue { + kind: SessionInputAnswerValueKind.Boolean; + value: boolean; +} + +export interface SessionInputSelectedAnswerValue { + kind: SessionInputAnswerValueKind.Selected; + value: string; + /** Free-form text entered instead of selecting an option */ + freeformValues?: string[]; +} + +export interface SessionInputSelectedManyAnswerValue { + kind: SessionInputAnswerValueKind.SelectedMany; + value: string[]; + /** Free-form text entered in addition to selected options */ + freeformValues?: string[]; +} + +export type SessionInputAnswerValue = SessionInputTextAnswerValue + | SessionInputNumberAnswerValue + | SessionInputBooleanAnswerValue + | SessionInputSelectedAnswerValue + | SessionInputSelectedManyAnswerValue; + +export interface SessionInputAnswered { + /** Answer state */ + state: SessionInputAnswerState.Draft | SessionInputAnswerState.Submitted; + /** Answer value */ + value: SessionInputAnswerValue; +} + +export interface SessionInputSkipped { + /** Answer state */ + state: SessionInputAnswerState.Skipped; + /** Free-form reason or value captured while skipping, if any */ + freeformValues?: string[]; +} + +/** + * Answer lifecycle state. + * + * @category Session Input Types + */ +export const enum SessionInputAnswerState { + Draft = 'draft', + Submitted = 'submitted', + Skipped = 'skipped', +} + +/** + * Draft, submitted, or skipped answer for one question. + * + * @category Session Input Types + */ +export type SessionInputAnswer = SessionInputAnswered | SessionInputSkipped; + +// ─── Turn Types ────────────────────────────────────────────────────────────── + +/** + * How a turn ended. + * + * @category Turn Types + */ +export const enum TurnState { + Complete = 'complete', + Cancelled = 'cancelled', + Error = 'error', +} + +/** + * Discriminant for {@link MessageAttachment} variants. + * + * @category Turn Types + */ +export const enum MessageAttachmentKind { + /** A simple, opaque attachment whose representation is described by the producer. */ + Simple = 'simple', + /** An attachment whose data is embedded inline as a base64 string. */ + EmbeddedResource = 'embeddedResource', + /** An attachment that references a resource by URI. */ + Resource = 'resource', + /** An attachment that references annotations on an annotations channel. */ + Annotations = 'annotations', +} + +/** + * A completed request/response cycle. + * + * @category Turn Types + */ +export interface Turn { + /** Turn identifier */ + id: string; + /** The message that initiated the turn */ + message: Message; + /** + * All response content in stream order: text, tool calls, reasoning, and content refs. + * + * Consumers should derive display text by concatenating markdown parts, + * and find tool calls by filtering for `ToolCall` parts. + */ + responseParts: ResponsePart[]; + /** Token usage info */ + usage: UsageInfo | undefined; + /** How the turn ended */ + state: TurnState; + /** Error details if state is `'error'` */ + error?: ErrorInfo; +} + +/** + * An in-progress turn — the assistant is actively streaming. + * + * @category Turn Types + */ +export interface ActiveTurn { + /** Turn identifier */ + id: string; + /** The message that initiated the turn */ + message: Message; + /** + * All response content in stream order: text, tool calls, reasoning, and content refs. + * + * Tool call parts include `pendingPermissions` when permissions are awaiting user approval. + */ + responseParts: ResponsePart[]; + /** Token usage info */ + usage: UsageInfo | undefined; +} + +/** + * Discriminant for Message types. + * + * @category Turn Types + */ +export enum MessageKind { + User = 'user', + SystemNotification = 'systemNotification', +} + +/** + * A message that initiates or steers a turn. Messages can originate from the + * user or be system-generated (see {@link MessageKind}). + * + * Attachments MAY be referenced inside {@link Message.text} via their + * {@link MessageAttachmentBase.range} field. Attachments without a range are + * still associated with the message but do not correspond to a specific span + * in the text. + * + * @category Turn Types + */ +export interface Message { + /** Message text */ + text: string; + /** The origin of the message */ + origin: { kind: MessageKind }; + /** File/selection attachments */ + attachments?: MessageAttachment[]; + /** + * Additional provider-specific metadata for this message. + * + * Clients MAY look for well-known keys here to provide enhanced UI, and + * agent hosts MAY use it to carry context that does not fit any other + * field. Mirrors the MCP `_meta` convention. + */ + _meta?: Record; +} + +/** + * Common fields shared by all {@link MessageAttachment} variants. + * + * @category Turn Types + */ +export interface MessageAttachmentBase { + /** + * A human-readable label for the attachment (e.g. the filename of a file + * attachment). Used for display in UI. + */ + label: string; + + /** + * If defined, the range in {@link Message.text} that references this + * attachment. This is a text range, not a byte range. + */ + range?: TextRange; + + /** + * Advisory display hint for clients rendering this attachment. Recognized + * values include: + * + * - `'image'`: the attachment is an image + * - `'document'`: the attachment is a textual document + * - `'symbol'`: the attachment is a code symbol (e.g. a function or class) + * - `'directory'`: the attachment is a folder + * - `'selection'`: the attachment is a selection within a document + * + * Implementations MAY provide additional values; clients SHOULD fall back + * to a reasonable default when an unknown value is encountered. + */ + displayKind?: string; + + /** + * Additional implementation-defined metadata for the attachment. + * + * If the attachment was produced by the `completions` command, the client + * MUST preserve every property of `_meta` originally returned by the agent + * host when sending the user message containing the accepted completion. + */ + _meta?: Record; +} + +/** + * A simple, opaque attachment whose model representation is described by + * the producer. + * + * @category Turn Types + */ +export interface SimpleMessageAttachment extends MessageAttachmentBase { + /** Discriminant */ + type: MessageAttachmentKind.Simple; + + /** + * Representation of the attachment as it should be shown to the model. + * + * If the attachment was produced by the client, this property MUST be + * defined so the agent host can correctly interpret the attachment. This + * property MAY be omitted when the attachment originated from a + * `completions` response. + */ + modelRepresentation?: string; +} + +/** + * An attachment whose data is embedded inline as a base64 string. + * + * Use this for small binary payloads (e.g. a pasted image) that should be + * delivered with the user message itself rather than fetched separately. + * + * @category Turn Types + */ +export interface MessageEmbeddedResourceAttachment extends MessageAttachmentBase { + /** Discriminant */ + type: MessageAttachmentKind.EmbeddedResource; + /** Base64-encoded binary data */ + data: string; + /** Content MIME type (e.g. `"image/png"`, `"application/pdf"`) */ + contentType: string; + /** + * Optional selection within the attached textual resource. + * + * Only meaningful for textual resources. + */ + selection?: TextSelection; +} + +/** + * An attachment that references a resource by URI. The content is not + * delivered inline; consumers can fetch it via `resourceRead` when needed. + * + * @category Turn Types + */ +export interface MessageResourceAttachment extends MessageAttachmentBase, ContentRef { + /** Discriminant */ + type: MessageAttachmentKind.Resource; + /** + * Optional selection within the referenced textual resource. + * + * Only meaningful for textual resources. + */ + selection?: TextSelection; +} + +/** + * An attachment that references annotations on a session's annotations + * channel (see {@link AnnotationsState}). + * + * When {@link annotationIds} is omitted the attachment references every + * annotation on the channel; when present it references only the listed + * {@link Annotation.id | annotation ids}. + * + * @category Turn Types + */ +export interface MessageAnnotationsAttachment extends MessageAttachmentBase { + /** Discriminant */ + type: MessageAttachmentKind.Annotations; + /** + * The annotations channel URI (typically `ahp-session://annotations`). + * Matches {@link AnnotationsSummary.resource}. + */ + resource: URI; + /** + * Specific {@link Annotation.id | annotation ids} to reference. When + * omitted, the attachment references all annotations on the channel. + */ + annotationIds?: string[]; +} + +/** + * An attachment associated with a {@link Message}. + * + * @category Turn Types + */ +export type MessageAttachment = + | SimpleMessageAttachment + | MessageEmbeddedResourceAttachment + | MessageResourceAttachment + | MessageAnnotationsAttachment; + +// ─── Response Parts ────────────────────────────────────────────────────────── + +/** + * Discriminant for response part types. + * + * @category Response Parts + */ +export const enum ResponsePartKind { + Markdown = 'markdown', + ContentRef = 'contentRef', + ToolCall = 'toolCall', + Reasoning = 'reasoning', + SystemNotification = 'systemNotification', +} + +/** + * @category Response Parts + */ +export interface MarkdownResponsePart { + /** Discriminant */ + kind: ResponsePartKind.Markdown; + /** Part identifier, used by `session/delta` to target this part for content appends */ + id: string; + /** Markdown content */ + content: string; +} + +/** + * A content part that's a reference to large content stored outside the state tree. + * + * @category Response Parts + */ +export interface ResourceReponsePart extends ContentRef { + /** Discriminant */ + kind: ResponsePartKind.ContentRef; +} + +/** + * A tool call represented as a response part. + * + * Tool calls are part of the response stream, interleaved with text and + * reasoning. The `toolCall.toolCallId` serves as the part identifier for + * actions that target this part. + * + * @category Response Parts + */ +export interface ToolCallResponsePart { + /** Discriminant */ + kind: ResponsePartKind.ToolCall; + /** Full tool call lifecycle state */ + toolCall: ToolCallState; +} + +/** + * Reasoning/thinking content from the model. + * + * @category Response Parts + */ +export interface ReasoningResponsePart { + /** Discriminant */ + kind: ResponsePartKind.Reasoning; + /** Part identifier, used by `session/reasoning` to target this part for content appends */ + id: string; + /** Accumulated reasoning text */ + content: string; +} + +/** + * @category Response Parts + */ +export type ResponsePart = + | MarkdownResponsePart + | ResourceReponsePart + | ToolCallResponsePart + | ReasoningResponsePart + | SystemNotificationResponsePart; + +/** + * A system notification surfaced as part of the response stream. + * + * System notifications are messages authored by the agent harness + * that need to be visible to both the agent (for situational awareness) and + * the user (for transcript continuity). Examples include "background subagent + * X completed" or "task Y was cancelled". + * + * @category Response Parts + */ +export interface SystemNotificationResponsePart { + /** Discriminant */ + kind: ResponsePartKind.SystemNotification; + /** The text of the system notification */ + content: StringOrMarkdown; +} + + +// ─── Tool Call Types ───────────────────────────────────────────────────────── + +/** + * Status of a tool call in the lifecycle state machine. + * + * @category Tool Call Types + */ +export const enum ToolCallStatus { + Streaming = 'streaming', + PendingConfirmation = 'pending-confirmation', + Running = 'running', + PendingResultConfirmation = 'pending-result-confirmation', + Completed = 'completed', + Cancelled = 'cancelled', +} + +/** + * How a tool call was confirmed for execution. + * + * - `NotNeeded` — No confirmation required (auto-approved) + * - `UserAction` — User explicitly approved + * - `Setting` — Approved by a persistent user setting + * + * @category Tool Call Types + */ +export const enum ToolCallConfirmationReason { + NotNeeded = 'not-needed', + UserAction = 'user-action', + Setting = 'setting', +} + +/** + * Why a tool call was cancelled. + * + * @category Tool Call Types + */ +export const enum ToolCallCancellationReason { + Denied = 'denied', + Skipped = 'skipped', + ResultDenied = 'result-denied', +} + +/** + * Whether a confirmation option represents an approval or denial action. + * + * @category Tool Call Types + */ +export const enum ConfirmationOptionKind { + Approve = 'approve', + Deny = 'deny', +} + +/** + * A confirmation option that the server offers for a tool call awaiting + * approval. Allows richer choices beyond simple approve/deny — for example, + * "Approve in this Session" or "Deny with reason." + * + * @category Tool Call Types + */ +export interface ConfirmationOption { + /** Unique identifier for the option, returned in the confirmed action */ + id: string; + /** Human-readable label displayed to the user */ + label: string; + /** Whether this option represents an approval or denial */ + kind: ConfirmationOptionKind; + /** + * Logical group number for visual categorisation. + * + * Clients SHOULD display options in the order they are defined and MAY + * use differing group numbers to insert dividers between logical clusters + * of options. + */ + group?: number; +} + +export const enum ToolCallContributorKind { + Client = 'client', + MCP = 'mcp', +} + +export interface ToolCallClientContributor { + kind: ToolCallContributorKind.Client; + /** + * If this tool is provided by a client, the `clientId` of the owning client. + * Absent for server-side tools. + * + * When set, the identified client is responsible for executing the tool and + * dispatching `session/toolCallComplete` with the result. + */ + clientId: string; +} + +export interface ToolCallMcpContributor { + kind: ToolCallContributorKind.MCP; + /** + * Customization ID of the corresponding MCP server in {@link SessionState.customizations}. + */ + customizationId: string; +} + +export type ToolCallContributor = ToolCallClientContributor | ToolCallMcpContributor; + +/** + * Metadata common to all tool call states. + * + * @category Tool Call Types + * @remarks + * Fields like `toolName` carry agent-specific identifiers on the wire despite the + * agent-agnostic design principle. These exist for debugging and logging purposes. + * A future version may move these to a separate diagnostic channel or namespace them + * more clearly. + */ +interface ToolCallBase { + /** Unique tool call identifier */ + toolCallId: string; + /** Internal tool name (for debugging/logging) */ + toolName: string; + /** Human-readable tool name */ + displayName: string; + /** + * Reference to the contributor of the tool being called. + */ + contributor?: ToolCallContributor; + /** + * Additional provider-specific metadata for this tool call. + * + * This MAY include a `ui` field corresponding to the MCP Apps (SEP-1865) + * `McpUiToolMeta` found in MCP tool calls, which may be used in combination + * with the {@link contributor} to serve MCP Apps. + */ + _meta?: Record; +} + +/** + * Properties available once tool call parameters are fully received. + * + * @category Tool Call Types + */ +interface ToolCallParameterFields { + /** Message describing what the tool will do */ + invocationMessage: StringOrMarkdown; + /** Raw tool input */ + toolInput?: string; +} + +/** + * Tool execution result details, available after execution completes. + * + * @category Tool Call Types + */ +export interface ToolCallResult { + /** Whether the tool succeeded */ + success: boolean; + /** Past-tense description of what the tool did */ + pastTenseMessage: StringOrMarkdown; + /** + * Unstructured result content blocks. + * + * This mirrors the `content` field of MCP `CallToolResult`. + */ + content?: ToolResultContent[]; + /** + * Optional structured result object. + * + * This mirrors the `structuredContent` field of MCP `CallToolResult`. + */ + structuredContent?: Record; + /** Error details if the tool failed */ + error?: { message: string; code?: string }; +} + +/** + * LM is streaming the tool call parameters. + * + * @category Tool Call Types + */ +export interface ToolCallStreamingState extends ToolCallBase { + status: ToolCallStatus.Streaming; + /** Partial parameters accumulated so far */ + partialInput?: string; + /** Progress message shown while parameters are streaming */ + invocationMessage?: StringOrMarkdown; +} + +/** + * Parameters are complete, or a running tool requires re-confirmation + * (e.g. a mid-execution permission check). + * + * @category Tool Call Types + */ +export interface ToolCallPendingConfirmationState extends ToolCallBase, ToolCallParameterFields { + status: ToolCallStatus.PendingConfirmation; + /** Short title for the confirmation prompt (e.g. `"Run in terminal"`, `"Write file"`) */ + confirmationTitle?: StringOrMarkdown; + /** File edits that this tool call will perform, for preview before confirmation */ + edits?: { items: FileEdit[] }; + /** Whether the agent host allows the client to edit the tool's input parameters before confirming */ + editable?: boolean; + /** + * Options the server offers for this confirmation. When present, the client + * SHOULD render these instead of a plain approve/deny UI. Each option + * belongs to a {@link ConfirmationOptionGroup} so the client can still + * categorise the choices. + */ + options?: ConfirmationOption[]; +} + +/** + * Tool is actively executing. + * + * @category Tool Call Types + */ +export interface ToolCallRunningState extends ToolCallBase, ToolCallParameterFields { + status: ToolCallStatus.Running; + /** How the tool was confirmed for execution */ + confirmed: ToolCallConfirmationReason; + /** The confirmation option the user selected, if confirmation options were provided */ + selectedOption?: ConfirmationOption; + /** + * Partial content produced while the tool is still executing. + * + * For example, a terminal content block lets clients subscribe to live + * output before the tool completes. + */ + content?: ToolResultContent[]; +} + +/** + * Tool finished executing, waiting for client to approve the result. + * + * @category Tool Call Types + */ +export interface ToolCallPendingResultConfirmationState extends ToolCallBase, ToolCallParameterFields, ToolCallResult { + status: ToolCallStatus.PendingResultConfirmation; + /** How the tool was confirmed for execution */ + confirmed: ToolCallConfirmationReason; + /** The confirmation option the user selected, if confirmation options were provided */ + selectedOption?: ConfirmationOption; +} + +/** + * Tool completed successfully or with an error. + * + * @category Tool Call Types + */ +export interface ToolCallCompletedState extends ToolCallBase, ToolCallParameterFields, ToolCallResult { + status: ToolCallStatus.Completed; + /** How the tool was confirmed for execution */ + confirmed: ToolCallConfirmationReason; + /** The confirmation option the user selected, if confirmation options were provided */ + selectedOption?: ConfirmationOption; +} + +/** + * Tool call was cancelled before execution. + * + * @category Tool Call Types + */ +export interface ToolCallCancelledState extends ToolCallBase, ToolCallParameterFields { + status: ToolCallStatus.Cancelled; + /** Why the tool was cancelled */ + reason: ToolCallCancellationReason; + /** Optional message explaining the cancellation */ + reasonMessage?: StringOrMarkdown; + /** What the user suggested doing instead */ + userSuggestion?: Message; + /** The confirmation option the user selected, if confirmation options were provided */ + selectedOption?: ConfirmationOption; +} + +/** + * Discriminated union of all tool call lifecycle states. + * + * See the [state model guide](/guide/state-model.html#tool-call-lifecycle) + * for the full state machine diagram. + * + * @category Tool Call Types + */ +export type ToolCallState = + | ToolCallStreamingState + | ToolCallPendingConfirmationState + | ToolCallRunningState + | ToolCallPendingResultConfirmationState + | ToolCallCompletedState + | ToolCallCancelledState; + // ─── Tool Definition Types ─────────────────────────────────────────────────── /** @@ -402,6 +1268,125 @@ export interface ToolAnnotations { openWorldHint?: boolean; } +// ─── Tool Result Content ───────────────────────────────────────────────────── + +/** + * Discriminant for tool result content types. + * + * @category Tool Result Content + */ +export const enum ToolResultContentType { + Text = 'text', + EmbeddedResource = 'embeddedResource', + Resource = 'resource', + FileEdit = 'fileEdit', + Terminal = 'terminal', + Subagent = 'subagent', +} + +/** + * Text content in a tool result. + * + * Mirrors MCP `TextContent`. + * + * @category Tool Result Content + */ +export interface ToolResultTextContent { + type: ToolResultContentType.Text; + /** The text content */ + text: string; +} + +/** + * Base64-encoded binary content embedded in a tool result. + * + * Mirrors MCP `EmbeddedResource` for inline binary data. + * + * @category Tool Result Content + */ +export interface ToolResultEmbeddedResourceContent { + type: ToolResultContentType.EmbeddedResource; + /** Base64-encoded data */ + data: string; + /** Content type (e.g. `"image/png"`, `"application/pdf"`) */ + contentType: string; +} + +/** + * A reference to a resource stored outside the tool result. + * + * Wraps {@link ContentRef} for lazy-loading large results. + * + * @category Tool Result Content + */ +export interface ToolResultResourceContent extends ContentRef { + type: ToolResultContentType.Resource; +} + +/** + * Describes a file modification performed by a tool. + * + * @category Tool Result Content + */ +export interface ToolResultFileEditContent extends FileEdit { + type: ToolResultContentType.FileEdit; +} + +/** + * A reference to a terminal whose output is relevant to this tool result. + * + * Clients can subscribe to the terminal's URI to stream its output in real + * time, providing live feedback while a tool is executing. + * + * @category Tool Result Content + */ +export interface ToolResultTerminalContent { + type: ToolResultContentType.Terminal; + /** Terminal URI (subscribable for full terminal state) */ + resource: URI; + /** Display title for the terminal content */ + title: string; +} + +/** + * A reference to a subagent session spawned by a tool. + * + * Clients can subscribe to the subagent's session URI to stream its + * progress in real time, including inner tool calls and responses. + * + * @category Tool Result Content + */ +export interface ToolResultSubagentContent { + type: ToolResultContentType.Subagent; + /** Subagent session URI (subscribable for full session state) */ + resource: URI; + /** Display title for the subagent */ + title: string; + /** Internal agent name */ + agentName?: string; + /** Human-readable description of the subagent's task */ + description?: string; +} + +/** + * Content block in a tool result. + * + * Mirrors the content blocks in MCP `CallToolResult.content`, plus + * `ToolResultResourceContent` for lazy-loading large results, + * `ToolResultFileEditContent` for file edit diffs, + * `ToolResultTerminalContent` for live terminal output, and + * `ToolResultSubagentContent` for subagent sessions (AHP extensions). + * + * @category Tool Result Content + */ +export type ToolResultContent = + | ToolResultTextContent + | ToolResultEmbeddedResourceContent + | ToolResultResourceContent + | ToolResultFileEditContent + | ToolResultTerminalContent + | ToolResultSubagentContent; + // ─── Customization Types ───────────────────────────────────────────────────── /** diff --git a/types/commands.ts b/types/commands.ts index c77dd949..03931e26 100644 --- a/types/commands.ts +++ b/types/commands.ts @@ -10,7 +10,6 @@ export * from './common/commands.js'; export * from './channels-root/commands.js'; export * from './channels-session/commands.js'; -export * from './channels-chat/commands.js'; export * from './channels-terminal/commands.js'; export * from './channels-changeset/commands.js'; export * from './channels-resource-watch/commands.js'; diff --git a/types/common/actions.ts b/types/common/actions.ts index 09c14f9f..4a8ddd04 100644 --- a/types/common/actions.ts +++ b/types/common/actions.ts @@ -18,21 +18,39 @@ import type { import type { SessionReadyAction, SessionCreationFailedAction, - SessionChatAddedAction, - SessionChatRemovedAction, - SessionChatUpdatedAction, - SessionDefaultChatChangedAction, + SessionTurnStartedAction, + SessionDeltaAction, + SessionResponsePartAction, + SessionToolCallStartAction, + SessionToolCallDeltaAction, + SessionToolCallReadyAction, + SessionToolCallConfirmedAction, + SessionToolCallCompleteAction, + SessionToolCallResultConfirmedAction, + SessionToolCallContentChangedAction, + SessionTurnCompleteAction, + SessionTurnCancelledAction, + SessionErrorAction, SessionTitleChangedAction, + SessionUsageAction, + SessionReasoningAction, SessionModelChangedAction, SessionAgentChangedAction, SessionServerToolsChangedAction, SessionActiveClientChangedAction, SessionActiveClientToolsChangedAction, + SessionPendingMessageSetAction, + SessionPendingMessageRemovedAction, + SessionQueuedMessagesReorderedAction, + SessionInputRequestedAction, + SessionInputAnswerChangedAction, + SessionInputCompletedAction, SessionCustomizationsChangedAction, SessionCustomizationToggledAction, SessionCustomizationUpdatedAction, SessionCustomizationRemovedAction, SessionMcpServerStateChangedAction, + SessionTruncatedAction, SessionIsReadChangedAction, SessionIsArchivedChangedAction, SessionActivityChangedAction, @@ -41,31 +59,6 @@ import type { SessionMetaChangedAction, } from '../channels-session/actions.js'; -import type { - ChatTurnStartedAction, - ChatDeltaAction, - ChatResponsePartAction, - ChatToolCallStartAction, - ChatToolCallDeltaAction, - ChatToolCallReadyAction, - ChatToolCallConfirmedAction, - ChatToolCallCompleteAction, - ChatToolCallResultConfirmedAction, - ChatToolCallContentChangedAction, - ChatTurnCompleteAction, - ChatTurnCancelledAction, - ChatErrorAction, - ChatUsageAction, - ChatReasoningAction, - ChatPendingMessageSetAction, - ChatPendingMessageRemovedAction, - ChatQueuedMessagesReorderedAction, - ChatInputRequestedAction, - ChatInputAnswerChangedAction, - ChatInputCompletedAction, - ChatTruncatedAction, -} from '../channels-chat/actions.js'; - import type { ChangesetStatusChangedAction, ChangesetFileSetAction, @@ -113,43 +106,39 @@ export const enum ActionType { RootActiveSessionsChanged = 'root/activeSessionsChanged', SessionReady = 'session/ready', SessionCreationFailed = 'session/creationFailed', - SessionChatAdded = 'session/chatAdded', - SessionChatRemoved = 'session/chatRemoved', - SessionChatUpdated = 'session/chatUpdated', - SessionDefaultChatChanged = 'session/defaultChatChanged', - ChatTurnStarted = 'chat/turnStarted', - ChatDelta = 'chat/delta', - ChatResponsePart = 'chat/responsePart', - ChatToolCallStart = 'chat/toolCallStart', - ChatToolCallDelta = 'chat/toolCallDelta', - ChatToolCallReady = 'chat/toolCallReady', - ChatToolCallConfirmed = 'chat/toolCallConfirmed', - ChatToolCallComplete = 'chat/toolCallComplete', - ChatToolCallResultConfirmed = 'chat/toolCallResultConfirmed', - ChatToolCallContentChanged = 'chat/toolCallContentChanged', - ChatTurnComplete = 'chat/turnComplete', - ChatTurnCancelled = 'chat/turnCancelled', - ChatError = 'chat/error', + SessionTurnStarted = 'session/turnStarted', + SessionDelta = 'session/delta', + SessionResponsePart = 'session/responsePart', + SessionToolCallStart = 'session/toolCallStart', + SessionToolCallDelta = 'session/toolCallDelta', + SessionToolCallReady = 'session/toolCallReady', + SessionToolCallConfirmed = 'session/toolCallConfirmed', + SessionToolCallComplete = 'session/toolCallComplete', + SessionToolCallResultConfirmed = 'session/toolCallResultConfirmed', + SessionToolCallContentChanged = 'session/toolCallContentChanged', + SessionTurnComplete = 'session/turnComplete', + SessionTurnCancelled = 'session/turnCancelled', + SessionError = 'session/error', SessionTitleChanged = 'session/titleChanged', - ChatUsage = 'chat/usage', - ChatReasoning = 'chat/reasoning', + SessionUsage = 'session/usage', + SessionReasoning = 'session/reasoning', SessionModelChanged = 'session/modelChanged', SessionAgentChanged = 'session/agentChanged', SessionServerToolsChanged = 'session/serverToolsChanged', SessionActiveClientChanged = 'session/activeClientChanged', SessionActiveClientToolsChanged = 'session/activeClientToolsChanged', - ChatPendingMessageSet = 'chat/pendingMessageSet', - ChatPendingMessageRemoved = 'chat/pendingMessageRemoved', - ChatQueuedMessagesReordered = 'chat/queuedMessagesReordered', - ChatInputRequested = 'chat/inputRequested', - ChatInputAnswerChanged = 'chat/inputAnswerChanged', - ChatInputCompleted = 'chat/inputCompleted', + SessionPendingMessageSet = 'session/pendingMessageSet', + SessionPendingMessageRemoved = 'session/pendingMessageRemoved', + SessionQueuedMessagesReordered = 'session/queuedMessagesReordered', + SessionInputRequested = 'session/inputRequested', + SessionInputAnswerChanged = 'session/inputAnswerChanged', + SessionInputCompleted = 'session/inputCompleted', SessionCustomizationsChanged = 'session/customizationsChanged', SessionCustomizationToggled = 'session/customizationToggled', SessionCustomizationUpdated = 'session/customizationUpdated', SessionCustomizationRemoved = 'session/customizationRemoved', SessionMcpServerStateChanged = 'session/mcpServerStateChanged', - ChatTruncated = 'chat/truncated', + SessionTruncated = 'session/truncated', SessionIsReadChanged = 'session/isReadChanged', SessionIsArchivedChanged = 'session/isArchivedChanged', SessionActivityChanged = 'session/activityChanged', @@ -223,49 +212,45 @@ export type StateAction = | RootConfigChangedAction | SessionReadyAction | SessionCreationFailedAction - | SessionChatAddedAction - | SessionChatRemovedAction - | SessionChatUpdatedAction - | SessionDefaultChatChangedAction + | SessionTurnStartedAction + | SessionDeltaAction + | SessionResponsePartAction + | SessionToolCallStartAction + | SessionToolCallDeltaAction + | SessionToolCallReadyAction + | SessionToolCallConfirmedAction + | SessionToolCallCompleteAction + | SessionToolCallResultConfirmedAction + | SessionToolCallContentChangedAction + | SessionTurnCompleteAction + | SessionTurnCancelledAction + | SessionErrorAction | SessionTitleChangedAction + | SessionUsageAction + | SessionReasoningAction | SessionModelChangedAction | SessionAgentChangedAction | SessionServerToolsChangedAction | SessionActiveClientChangedAction | SessionActiveClientToolsChangedAction + | SessionPendingMessageSetAction + | SessionPendingMessageRemovedAction + | SessionQueuedMessagesReorderedAction + | SessionInputRequestedAction + | SessionInputAnswerChangedAction + | SessionInputCompletedAction | SessionCustomizationsChangedAction | SessionCustomizationToggledAction | SessionCustomizationUpdatedAction | SessionCustomizationRemovedAction | SessionMcpServerStateChangedAction + | SessionTruncatedAction | SessionIsReadChangedAction | SessionIsArchivedChangedAction | SessionActivityChangedAction | SessionChangesetsChangedAction | SessionConfigChangedAction | SessionMetaChangedAction - | ChatTurnStartedAction - | ChatDeltaAction - | ChatResponsePartAction - | ChatToolCallStartAction - | ChatToolCallDeltaAction - | ChatToolCallReadyAction - | ChatToolCallConfirmedAction - | ChatToolCallCompleteAction - | ChatToolCallResultConfirmedAction - | ChatToolCallContentChangedAction - | ChatTurnCompleteAction - | ChatTurnCancelledAction - | ChatErrorAction - | ChatUsageAction - | ChatReasoningAction - | ChatPendingMessageSetAction - | ChatPendingMessageRemovedAction - | ChatQueuedMessagesReorderedAction - | ChatInputRequestedAction - | ChatInputAnswerChangedAction - | ChatInputCompletedAction - | ChatTruncatedAction | ChangesetStatusChangedAction | ChangesetFileSetAction | ChangesetFileRemovedAction diff --git a/types/common/messages.ts b/types/common/messages.ts index fe422f37..f8d8efa4 100644 --- a/types/common/messages.ts +++ b/types/common/messages.ts @@ -54,10 +54,6 @@ import type { CompletionsParams, CompletionsResult, } from '../channels-session/commands.js'; -import type { - CreateChatParams, - DisposeChatParams, -} from '../channels-chat/commands.js'; import type { CreateTerminalParams, DisposeTerminalParams, @@ -152,8 +148,6 @@ export interface CommandMap { 'subscribe': { params: SubscribeParams; result: SubscribeResult }; 'createSession': { params: CreateSessionParams; result: null }; 'disposeSession': { params: DisposeSessionParams; result: null }; - 'createChat': { params: CreateChatParams; result: null }; - 'disposeChat': { params: DisposeChatParams; result: null }; 'createTerminal': { params: CreateTerminalParams; result: null }; 'disposeTerminal': { params: DisposeTerminalParams; result: null }; 'createResourceWatch': { params: CreateResourceWatchParams; result: CreateResourceWatchResult }; diff --git a/types/common/state.ts b/types/common/state.ts index 8c232693..fd22f7f9 100644 --- a/types/common/state.ts +++ b/types/common/state.ts @@ -16,7 +16,7 @@ import type { AnnotationsState } from '../channels-annotations/state.js'; // ─── Type Aliases ──────────────────────────────────────────────────────────── -/** A URI string (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`). */ +/** A URI string (e.g. `ahp-root://` or `ahp-session:/`). */ export type URI = string; /** @@ -322,7 +322,7 @@ export interface ErrorInfo { * @category Common Types */ export interface Snapshot { - /** The subscribed channel URI (e.g. `ahp-root://`, `ahp-session:/`, or `ahp-chat:/`) */ + /** The subscribed channel URI (e.g. `ahp-root://` or `ahp-session:/`) */ resource: URI; /** The current state of the resource */ state: RootState | SessionState | TerminalState | ChangesetState | ResourceWatchState | AnnotationsState; diff --git a/types/index.ts b/types/index.ts index a2459162..5a250428 100644 --- a/types/index.ts +++ b/types/index.ts @@ -21,9 +21,6 @@ export type { AgentSelection, SessionState, SessionSummary, - ChatState, - ChatSummary, - ChatOrigin, ChangesSummary, SessionConfigState, Turn, @@ -65,23 +62,23 @@ export type { ToolResultSubagentContent, SessionActiveClient, PendingMessage, - ChatInputAnswer, - ChatInputAnswerValue, - ChatInputTextAnswerValue, - ChatInputNumberAnswerValue, - ChatInputBooleanAnswerValue, - ChatInputSelectedAnswerValue, - ChatInputSelectedManyAnswerValue, - ChatInputAnswered, - ChatInputSkipped, - ChatInputOption, - ChatInputQuestion, - ChatInputTextQuestion, - ChatInputNumberQuestion, - ChatInputBooleanQuestion, - ChatInputSingleSelectQuestion, - ChatInputMultiSelectQuestion, - ChatInputRequest, + SessionInputAnswer, + SessionInputAnswerValue, + SessionInputTextAnswerValue, + SessionInputNumberAnswerValue, + SessionInputBooleanAnswerValue, + SessionInputSelectedAnswerValue, + SessionInputSelectedManyAnswerValue, + SessionInputAnswered, + SessionInputSkipped, + SessionInputOption, + SessionInputQuestion, + SessionInputTextQuestion, + SessionInputNumberQuestion, + SessionInputBooleanQuestion, + SessionInputSingleSelectQuestion, + SessionInputMultiSelectQuestion, + SessionInputRequest, UsageInfo, ErrorInfo, Snapshot, @@ -110,9 +107,7 @@ export { PolicyState, SessionLifecycle, SessionStatus, - ChatOriginKind, TurnState, - MessageKind, MessageAttachmentKind, ResponsePartKind, ToolCallStatus, @@ -121,10 +116,10 @@ export { ConfirmationOptionKind, ToolResultContentType, PendingMessageKind, - ChatInputAnswerState, - ChatInputAnswerValueKind, - ChatInputQuestionKind, - ChatInputResponseKind, + SessionInputAnswerState, + SessionInputAnswerValueKind, + SessionInputQuestionKind, + SessionInputResponseKind, TerminalClaimKind, ChangesetStatus, ChangesetOperationStatus, @@ -140,40 +135,36 @@ export type { RootActiveSessionsChangedAction, SessionReadyAction, SessionCreationFailedAction, - SessionChatAddedAction, - SessionChatRemovedAction, - SessionChatUpdatedAction, - SessionDefaultChatChangedAction, - ChatTurnStartedAction, - ChatDeltaAction, - ChatResponsePartAction, - ChatToolCallStartAction, - ChatToolCallDeltaAction, - ChatToolCallReadyAction, - ChatToolCallApprovedAction, - ChatToolCallDeniedAction, - ChatToolCallConfirmedAction, - ChatToolCallCompleteAction, - ChatToolCallResultConfirmedAction, - ChatToolCallContentChangedAction, - ChatTurnCompleteAction, - ChatTurnCancelledAction, - ChatErrorAction, + SessionTurnStartedAction, + SessionDeltaAction, + SessionResponsePartAction, + SessionToolCallStartAction, + SessionToolCallDeltaAction, + SessionToolCallReadyAction, + SessionToolCallApprovedAction, + SessionToolCallDeniedAction, + SessionToolCallConfirmedAction, + SessionToolCallCompleteAction, + SessionToolCallResultConfirmedAction, + SessionToolCallContentChangedAction, + SessionTurnCompleteAction, + SessionTurnCancelledAction, + SessionErrorAction, SessionTitleChangedAction, - ChatUsageAction, - ChatReasoningAction, + SessionUsageAction, + SessionReasoningAction, SessionModelChangedAction, SessionAgentChangedAction, SessionServerToolsChangedAction, SessionActiveClientChangedAction, SessionActiveClientToolsChangedAction, - ChatPendingMessageSetAction, - ChatPendingMessageRemovedAction, - ChatQueuedMessagesReorderedAction, - ChatInputAnswerChangedAction, - ChatInputCompletedAction, - ChatInputRequestedAction, - ChatTruncatedAction, + SessionPendingMessageSetAction, + SessionPendingMessageRemovedAction, + SessionQueuedMessagesReorderedAction, + SessionInputAnswerChangedAction, + SessionInputCompletedAction, + SessionInputRequestedAction, + SessionTruncatedAction, SessionIsReadChangedAction, SessionIsArchivedChangedAction, SessionActivityChangedAction, @@ -213,9 +204,6 @@ export type { SessionAction, ClientSessionAction, ServerSessionAction, - ChatAction, - ClientChatAction, - ServerChatAction, TerminalAction, ClientTerminalAction, ServerTerminalAction, @@ -236,7 +224,6 @@ export { IS_CLIENT_DISPATCHABLE } from './action-origin.generated.js'; export { rootReducer, sessionReducer, - chatReducer, terminalReducer, changesetReducer, annotationsReducer, @@ -259,9 +246,6 @@ export type { CreateSessionParams, SessionForkSource, DisposeSessionParams, - CreateChatParams, - ChatForkSource, - DisposeChatParams, CreateTerminalParams, DisposeTerminalParams, ListSessionsParams, diff --git a/types/messages.test.ts b/types/messages.test.ts index 591a9d7c..264a1ecb 100644 --- a/types/messages.test.ts +++ b/types/messages.test.ts @@ -28,7 +28,6 @@ function readChannelSources(baseName: string): string { 'common', 'channels-root', 'channels-session', - 'channels-chat', 'channels-terminal', 'channels-changeset', 'channels-annotations', diff --git a/types/reducers.test.ts b/types/reducers.test.ts index f3a8c7de..4a99a3dd 100644 --- a/types/reducers.test.ts +++ b/types/reducers.test.ts @@ -20,7 +20,6 @@ import { fileURLToPath } from 'node:url'; import { rootReducer, sessionReducer, - chatReducer, terminalReducer, changesetReducer, annotationsReducer, @@ -29,8 +28,9 @@ import { } from './reducers.js'; import { IS_CLIENT_DISPATCHABLE } from './action-origin.generated.js'; import { ActionType } from './actions.js'; -import type { RootState, SessionState, ChatState, TerminalState, ChangesetState, AnnotationsState, ResourceWatchState } from './state.js'; +import type { RootState, SessionState, TerminalState, ChangesetState, AnnotationsState, ResourceWatchState } from './state.js'; import { + SessionLifecycle, SessionStatus, TurnState, MessageKind, @@ -49,7 +49,6 @@ function readChannelSources(baseName: string): string { 'common', 'channels-root', 'channels-session', - 'channels-chat', 'channels-terminal', 'channels-changeset', 'channels-annotations', @@ -69,11 +68,11 @@ function readChannelSources(baseName: string): string { // ─── Fixture Loading ───────────────────────────────────────────────────────── -type FixtureState = RootState | SessionState | ChatState | TerminalState | ChangesetState | AnnotationsState | ResourceWatchState; +type FixtureState = RootState | SessionState | TerminalState | ChangesetState | AnnotationsState | ResourceWatchState; interface Fixture { description: string; - reducer: 'root' | 'session' | 'chat' | 'terminal' | 'changeset' | 'annotations' | 'resourceWatch'; + reducer: 'root' | 'session' | 'terminal' | 'changeset' | 'annotations' | 'resourceWatch'; initial: FixtureState; actions: unknown[]; expected: FixtureState; @@ -130,8 +129,6 @@ describe('reducer fixtures', () => { for (const action of fixture.actions) { if (fixture.reducer === 'root') { state = rootReducer(state as RootState, action as any); - } else if (fixture.reducer === 'chat') { - state = chatReducer(state as ChatState, action as any); } else if (fixture.reducer === 'terminal') { state = terminalReducer(state as TerminalState, action as any); } else if (fixture.reducer === 'changeset') { @@ -211,7 +208,7 @@ describe('IS_CLIENT_DISPATCHABLE', () => { describe('isClientDispatchable', () => { it('returns true for client-dispatchable actions', () => { - const action = { type: ActionType.ChatTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; + const action = { type: ActionType.SessionTurnStarted, turnId: 't', message: { text: 'Hello', origin: { kind: MessageKind.User } } } as const; assert.equal(isClientDispatchable(action), true); }); @@ -234,16 +231,17 @@ describe('reducer immutability', () => { assert.deepStrictEqual(state.agents, []); }); - it('chatReducer does not mutate original turns array', () => { + it('sessionReducer does not mutate original turns array', () => { const turn1 = { id: 't1', message: { text: 'First', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; const turn2 = { id: 't2', message: { text: 'Second', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; const turn3 = { id: 't3', message: { text: 'Third', origin: { kind: MessageKind.User } }, responseParts: [], usage: undefined, state: TurnState.Complete }; - const state: ChatState = { - summary: { resource: 'x', title: 'T', status: SessionStatus.Idle, modifiedAt: 1000 }, + const state: SessionState = { + summary: { resource: 'x', provider: 'copilot', title: 'T', status: SessionStatus.Idle, createdAt: 1000, modifiedAt: 1000, project: { uri: 'file:///test-project', displayName: 'Test Project' } }, + lifecycle: SessionLifecycle.Ready, turns: [turn1, turn2, turn3], }; const original = [...state.turns]; - chatReducer(state, { type: ActionType.ChatTruncated, turnId: 't1' }); + sessionReducer(state, { type: ActionType.SessionTruncated, turnId: 't1' }); assert.deepStrictEqual(state.turns, original); }); }); diff --git a/types/reducers.ts b/types/reducers.ts index d916ba3a..eb2d615a 100644 --- a/types/reducers.ts +++ b/types/reducers.ts @@ -7,7 +7,6 @@ export { rootReducer } from './channels-root/reducer.js'; export { sessionReducer } from './channels-session/reducer.js'; -export { chatReducer } from './channels-chat/reducer.js'; export { terminalReducer } from './channels-terminal/reducer.js'; export { changesetReducer } from './channels-changeset/reducer.js'; export { annotationsReducer } from './channels-annotations/reducer.js'; diff --git a/types/state.ts b/types/state.ts index 8748e48d..03de20b7 100644 --- a/types/state.ts +++ b/types/state.ts @@ -10,7 +10,6 @@ export * from './common/state.js'; export * from './channels-root/state.js'; export * from './channels-session/state.js'; -export * from './channels-chat/state.js'; export * from './channels-terminal/state.js'; export * from './channels-changeset/state.js'; export * from './channels-annotations/state.js'; diff --git a/types/test-cases/reducers/003-session-ready.json b/types/test-cases/reducers/003-session-ready.json index 575f9c79..8f17b7fb 100644 --- a/types/test-cases/reducers/003-session-ready.json +++ b/types/test-cases/reducers/003-session-ready.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -28,6 +28,6 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/004-session-creationfailed.json b/types/test-cases/reducers/004-session-creationfailed.json index aa25cdd6..24564838 100644 --- a/types/test-cases/reducers/004-session-creationfailed.json +++ b/types/test-cases/reducers/004-session-creationfailed.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -32,10 +32,10 @@ "modifiedAt": 1000 }, "lifecycle": "creationFailed", + "turns": [], "creationError": { "errorType": "init", "message": "Failed to start" - }, - "chats": [] + } } } diff --git a/types/test-cases/reducers/005-session-turnstarted.json b/types/test-cases/reducers/005-session-turnstarted.json index 3c3576ad..30bcdd89 100644 --- a/types/test-cases/reducers/005-session-turnstarted.json +++ b/types/test-cases/reducers/005-session-turnstarted.json @@ -1,16 +1,21 @@ { "description": "session/turnStarted", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [] }, "actions": [ { - "type": "chat/turnStarted", + "type": "session/turnStarted", "turnId": "turn-1", "message": { "text": "Hello", @@ -21,6 +26,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -32,10 +46,6 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:09.999Z" + } } } diff --git a/types/test-cases/reducers/006-turnstarted-with-queuedmessageid-removes-from-queuedmessages.json b/types/test-cases/reducers/006-turnstarted-with-queuedmessageid-removes-from-queuedmessages.json index 8636d1b2..ffd5c5fc 100644 --- a/types/test-cases/reducers/006-turnstarted-with-queuedmessageid-removes-from-queuedmessages.json +++ b/types/test-cases/reducers/006-turnstarted-with-queuedmessageid-removes-from-queuedmessages.json @@ -1,7 +1,16 @@ { "description": "turnStarted with queuedMessageId removes from queuedMessages", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [], "queuedMessages": [ { @@ -22,15 +31,11 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/turnStarted", + "type": "session/turnStarted", "turnId": "turn-1", "message": { "text": "First", @@ -42,18 +47,16 @@ } ], "expected": { - "turns": [], - "activeTurn": { - "id": "turn-1", - "message": { - "text": "First", - "origin": { - "kind": "user" - } - }, - "responseParts": [], - "usage": null + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 9999 }, + "lifecycle": "ready", + "turns": [], "queuedMessages": [ { "id": "q-2", @@ -65,9 +68,16 @@ } } ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": { + "id": "turn-1", + "message": { + "text": "First", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null + } } } diff --git a/types/test-cases/reducers/007-turnstarted-with-queuedmessageid-removes-last-queued-message.json b/types/test-cases/reducers/007-turnstarted-with-queuedmessageid-removes-last-queued-message.json index 15b4ecf4..2cdab800 100644 --- a/types/test-cases/reducers/007-turnstarted-with-queuedmessageid-removes-last-queued-message.json +++ b/types/test-cases/reducers/007-turnstarted-with-queuedmessageid-removes-last-queued-message.json @@ -1,7 +1,16 @@ { "description": "turnStarted with queuedMessageId removes last queued message", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [], "queuedMessages": [ { @@ -13,15 +22,11 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/turnStarted", + "type": "session/turnStarted", "turnId": "turn-1", "message": { "text": "Only", @@ -33,7 +38,17 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], + "queuedMessages": null, "activeTurn": { "id": "turn-1", "message": { @@ -44,11 +59,6 @@ }, "responseParts": [], "usage": null - }, - "queuedMessages": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:09.999Z" + } } } diff --git a/types/test-cases/reducers/008-turnstarted-with-queuedmessageid-removes-matching-steering-message.json b/types/test-cases/reducers/008-turnstarted-with-queuedmessageid-removes-matching-steering-message.json index c955affc..2fba0ce0 100644 --- a/types/test-cases/reducers/008-turnstarted-with-queuedmessageid-removes-matching-steering-message.json +++ b/types/test-cases/reducers/008-turnstarted-with-queuedmessageid-removes-matching-steering-message.json @@ -1,7 +1,16 @@ { "description": "turnStarted with queuedMessageId removes matching steering message", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [], "steeringMessage": { "id": "s-1", @@ -11,15 +20,11 @@ "kind": "user" } } - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + } }, "actions": [ { - "type": "chat/turnStarted", + "type": "session/turnStarted", "turnId": "turn-1", "message": { "text": "Steer", @@ -31,7 +36,17 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], + "steeringMessage": null, "activeTurn": { "id": "turn-1", "message": { @@ -42,11 +57,6 @@ }, "responseParts": [], "usage": null - }, - "steeringMessage": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:09.999Z" + } } } diff --git a/types/test-cases/reducers/009-turnstarted-without-queuedmessageid-does-not-touch-pending-messages.json b/types/test-cases/reducers/009-turnstarted-without-queuedmessageid-does-not-touch-pending-messages.json index 4b1418e5..1f5da132 100644 --- a/types/test-cases/reducers/009-turnstarted-without-queuedmessageid-does-not-touch-pending-messages.json +++ b/types/test-cases/reducers/009-turnstarted-without-queuedmessageid-does-not-touch-pending-messages.json @@ -1,7 +1,16 @@ { "description": "turnStarted without queuedMessageId does not touch pending messages", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [], "steeringMessage": { "id": "s-1", @@ -22,15 +31,11 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/turnStarted", + "type": "session/turnStarted", "turnId": "turn-1", "message": { "text": "Hello", @@ -41,18 +46,16 @@ } ], "expected": { - "turns": [], - "activeTurn": { - "id": "turn-1", - "message": { - "text": "Hello", - "origin": { - "kind": "user" - } - }, - "responseParts": [], - "usage": null + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 9999 }, + "lifecycle": "ready", + "turns": [], "steeringMessage": { "id": "s-1", "message": { @@ -73,9 +76,16 @@ } } ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": { + "id": "turn-1", + "message": { + "text": "Hello", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null + } } } diff --git a/types/test-cases/reducers/010-session-delta-appends-content.json b/types/test-cases/reducers/010-session-delta-appends-content.json index f4cefe7e..12de3271 100644 --- a/types/test-cases/reducers/010-session-delta-appends-content.json +++ b/types/test-cases/reducers/010-session-delta-appends-content.json @@ -1,7 +1,16 @@ { "description": "session/delta appends content", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/responsePart", + "type": "session/responsePart", "turnId": "turn-1", "part": { "kind": "markdown", @@ -30,19 +35,28 @@ } }, { - "type": "chat/delta", + "type": "session/delta", "turnId": "turn-1", "partId": "md-1", "content": "Hello " }, { - "type": "chat/delta", + "type": "session/delta", "turnId": "turn-1", "partId": "md-1", "content": "world" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -60,10 +74,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/011-session-delta-with-wrong-turnid-is-no-op.json b/types/test-cases/reducers/011-session-delta-with-wrong-turnid-is-no-op.json index c7fef529..e0152300 100644 --- a/types/test-cases/reducers/011-session-delta-with-wrong-turnid-is-no-op.json +++ b/types/test-cases/reducers/011-session-delta-with-wrong-turnid-is-no-op.json @@ -1,7 +1,16 @@ { "description": "session/delta with wrong turnId is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -19,21 +28,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/delta", + "type": "session/delta", "turnId": "wrong-turn", "partId": "md-1", "content": "orphan" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -51,10 +65,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/012-session-delta-without-activeturn-is-no-op.json b/types/test-cases/reducers/012-session-delta-without-activeturn-is-no-op.json index 604b4a55..6f6b1f60 100644 --- a/types/test-cases/reducers/012-session-delta-without-activeturn-is-no-op.json +++ b/types/test-cases/reducers/012-session-delta-without-activeturn-is-no-op.json @@ -1,26 +1,36 @@ { "description": "session/delta without activeTurn is no-op", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] }, "actions": [ { - "type": "chat/delta", + "type": "session/delta", "turnId": "turn-1", "partId": "md-1", "content": "orphan" } ], "expected": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] } } diff --git a/types/test-cases/reducers/013-session-responsepart-adds-to-responseparts.json b/types/test-cases/reducers/013-session-responsepart-adds-to-responseparts.json index 5ea45c46..3708ca8b 100644 --- a/types/test-cases/reducers/013-session-responsepart-adds-to-responseparts.json +++ b/types/test-cases/reducers/013-session-responsepart-adds-to-responseparts.json @@ -1,7 +1,16 @@ { "description": "session/responsePart adds to responseParts", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/responsePart", + "type": "session/responsePart", "turnId": "turn-1", "part": { "kind": "markdown", @@ -31,6 +36,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -48,10 +62,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/014-session-turncomplete-finalizes-turn.json b/types/test-cases/reducers/014-session-turncomplete-finalizes-turn.json index 2e4f14b9..382753b3 100644 --- a/types/test-cases/reducers/014-session-turncomplete-finalizes-turn.json +++ b/types/test-cases/reducers/014-session-turncomplete-finalizes-turn.json @@ -1,7 +1,16 @@ { "description": "session/turnComplete finalizes turn", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/responsePart", + "type": "session/responsePart", "turnId": "turn-1", "part": { "kind": "markdown", @@ -30,17 +35,26 @@ } }, { - "type": "chat/delta", + "type": "session/delta", "turnId": "turn-1", "partId": "md-1", "content": "Response text" }, { - "type": "chat/turnComplete", + "type": "session/turnComplete", "turnId": "turn-1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "turn-1", @@ -62,10 +76,6 @@ "error": null } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/015-session-turncancelled-finalizes-turn.json b/types/test-cases/reducers/015-session-turncancelled-finalizes-turn.json index 61677b59..c53aac51 100644 --- a/types/test-cases/reducers/015-session-turncancelled-finalizes-turn.json +++ b/types/test-cases/reducers/015-session-turncancelled-finalizes-turn.json @@ -1,7 +1,16 @@ { "description": "session/turnCancelled finalizes turn", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,19 +22,24 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/turnCancelled", + "type": "session/turnCancelled", "turnId": "turn-1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "turn-1", @@ -41,10 +55,6 @@ "error": null } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/016-session-error-finalizes-turn-with-error.json b/types/test-cases/reducers/016-session-error-finalizes-turn-with-error.json index 40c41bbd..232db4a8 100644 --- a/types/test-cases/reducers/016-session-error-finalizes-turn-with-error.json +++ b/types/test-cases/reducers/016-session-error-finalizes-turn-with-error.json @@ -1,7 +1,16 @@ { "description": "session/error finalizes turn with error", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/error", + "type": "session/error", "turnId": "turn-1", "error": { "errorType": "runtime", @@ -30,6 +35,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 2, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "turn-1", @@ -48,10 +62,6 @@ } } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 2, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/017-turncomplete-force-cancels-in-progress-tool-calls.json b/types/test-cases/reducers/017-turncomplete-force-cancels-in-progress-tool-calls.json index 7e13a9b1..17e0a28f 100644 --- a/types/test-cases/reducers/017-turncomplete-force-cancels-in-progress-tool-calls.json +++ b/types/test-cases/reducers/017-turncomplete-force-cancels-in-progress-tool-calls.json @@ -1,7 +1,16 @@ { "description": "turnComplete force-cancels in-progress tool calls", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,26 +22,31 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/turnComplete", + "type": "session/turnComplete", "turnId": "turn-1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "turn-1", @@ -63,10 +77,6 @@ "error": null } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/018-turncomplete-with-wrong-turnid-is-no-op.json b/types/test-cases/reducers/018-turncomplete-with-wrong-turnid-is-no-op.json index 42df2388..8f3335ec 100644 --- a/types/test-cases/reducers/018-turncomplete-with-wrong-turnid-is-no-op.json +++ b/types/test-cases/reducers/018-turncomplete-with-wrong-turnid-is-no-op.json @@ -1,7 +1,16 @@ { "description": "turnComplete with wrong turnId is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,19 +22,24 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/turnComplete", + "type": "session/turnComplete", "turnId": "wrong-turn" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -37,10 +51,6 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/019-tool-call-full-lifecycle-start-delta-ready-confirmed-complete.json b/types/test-cases/reducers/019-tool-call-full-lifecycle-start-delta-ready-confirmed-complete.json index aacc8adf..d4ac1ec5 100644 --- a/types/test-cases/reducers/019-tool-call-full-lifecycle-start-delta-ready-confirmed-complete.json +++ b/types/test-cases/reducers/019-tool-call-full-lifecycle-start-delta-ready-confirmed-complete.json @@ -1,7 +1,16 @@ { "description": "tool call full lifecycle: start → delta → ready → confirmed → complete", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,43 +22,39 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallDelta", + "type": "session/toolCallDelta", "turnId": "turn-1", "toolCallId": "tc-1", "content": "ls -la", "invocationMessage": "Listing files" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: ls -la", "toolInput": "ls -la" }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true, "confirmed": "user-action" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -59,6 +64,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -87,10 +101,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/020-tool-call-ready-with-auto-confirm-transitions-to-running.json b/types/test-cases/reducers/020-tool-call-ready-with-auto-confirm-transitions-to-running.json index 998896e5..c61b0d5d 100644 --- a/types/test-cases/reducers/020-tool-call-ready-with-auto-confirm-transitions-to-running.json +++ b/types/test-cases/reducers/020-tool-call-ready-with-auto-confirm-transitions-to-running.json @@ -1,7 +1,16 @@ { "description": "tool call ready with auto-confirm transitions to running", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,22 +22,18 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run", @@ -36,6 +41,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -62,10 +76,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/021-tool-call-denied-transitions-to-cancelled.json b/types/test-cases/reducers/021-tool-call-denied-transitions-to-cancelled.json index da4d8c5f..d76222d7 100644 --- a/types/test-cases/reducers/021-tool-call-denied-transitions-to-cancelled.json +++ b/types/test-cases/reducers/021-tool-call-denied-transitions-to-cancelled.json @@ -1,7 +1,16 @@ { "description": "tool call denied transitions to cancelled", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,28 +22,24 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: rm -rf /" }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": false, @@ -42,6 +47,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -70,10 +84,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/022-tool-call-result-confirmation-pending-approved.json b/types/test-cases/reducers/022-tool-call-result-confirmation-pending-approved.json index 41a81c1c..238d17dc 100644 --- a/types/test-cases/reducers/022-tool-call-result-confirmation-pending-approved.json +++ b/types/test-cases/reducers/022-tool-call-result-confirmation-pending-approved.json @@ -1,7 +1,16 @@ { "description": "tool call result confirmation → pending → approved", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,29 +22,25 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run", "confirmed": "not-needed" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -45,13 +50,22 @@ "requiresResultConfirmation": true }, { - "type": "chat/toolCallResultConfirmed", + "type": "session/toolCallResultConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -83,10 +97,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/023-tool-call-result-denied-cancelled-with-result-denied-reason.json b/types/test-cases/reducers/023-tool-call-result-denied-cancelled-with-result-denied-reason.json index 4da3beee..3629807a 100644 --- a/types/test-cases/reducers/023-tool-call-result-denied-cancelled-with-result-denied-reason.json +++ b/types/test-cases/reducers/023-tool-call-result-denied-cancelled-with-result-denied-reason.json @@ -1,7 +1,16 @@ { "description": "tool call result denied → cancelled with result-denied reason", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,29 +22,25 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run", "confirmed": "not-needed" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -45,13 +50,22 @@ "requiresResultConfirmation": true }, { - "type": "chat/toolCallResultConfirmed", + "type": "session/toolCallResultConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": false } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -78,10 +92,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/024-tool-call-complete-from-pending-confirmation-defaults-confirmed.json b/types/test-cases/reducers/024-tool-call-complete-from-pending-confirmation-defaults-confirmed.json index 209bfdca..df5d0904 100644 --- a/types/test-cases/reducers/024-tool-call-complete-from-pending-confirmation-defaults-confirmed.json +++ b/types/test-cases/reducers/024-tool-call-complete-from-pending-confirmation-defaults-confirmed.json @@ -1,7 +1,16 @@ { "description": "tool call complete from pending-confirmation defaults confirmed", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,28 +22,24 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -44,6 +49,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -72,10 +86,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/025-tool-call-actions-for-unknown-toolcallid-are-no-op.json b/types/test-cases/reducers/025-tool-call-actions-for-unknown-toolcallid-are-no-op.json index b4c0d1d9..8f130d70 100644 --- a/types/test-cases/reducers/025-tool-call-actions-for-unknown-toolcallid-are-no-op.json +++ b/types/test-cases/reducers/025-tool-call-actions-for-unknown-toolcallid-are-no-op.json @@ -1,7 +1,16 @@ { "description": "tool call actions for unknown toolCallId are no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,21 +22,26 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallDelta", + "type": "session/toolCallDelta", "turnId": "turn-1", "toolCallId": "nonexistent", "content": "data" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -39,10 +53,6 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/026-toolcallready-transitions-running-tool-back-to-pending-confirmation.json b/types/test-cases/reducers/026-toolcallready-transitions-running-tool-back-to-pending-confirmation.json index 7f726896..90433f29 100644 --- a/types/test-cases/reducers/026-toolcallready-transitions-running-tool-back-to-pending-confirmation.json +++ b/types/test-cases/reducers/026-toolcallready-transitions-running-tool-back-to-pending-confirmation.json @@ -1,7 +1,16 @@ { "description": "toolCallReady transitions running tool back to pending-confirmation", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,29 +22,25 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run", "confirmed": "not-needed" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: rm -rf /tmp/test", @@ -46,6 +51,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -77,10 +91,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/027-toolcallready-re-confirmation-approved-transitions-back-to-running.json b/types/test-cases/reducers/027-toolcallready-re-confirmation-approved-transitions-back-to-running.json index 2c73cba5..e0628e8a 100644 --- a/types/test-cases/reducers/027-toolcallready-re-confirmation-approved-transitions-back-to-running.json +++ b/types/test-cases/reducers/027-toolcallready-re-confirmation-approved-transitions-back-to-running.json @@ -1,7 +1,16 @@ { "description": "toolCallReady re-confirmation approved transitions back to running", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,35 +22,31 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run", "confirmed": "not-needed" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Permission needed" }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true, @@ -49,6 +54,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -75,10 +89,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/028-toolcallready-re-confirmation-denied-transitions-to-cancelled.json b/types/test-cases/reducers/028-toolcallready-re-confirmation-denied-transitions-to-cancelled.json index df31e6b1..48d208dd 100644 --- a/types/test-cases/reducers/028-toolcallready-re-confirmation-denied-transitions-to-cancelled.json +++ b/types/test-cases/reducers/028-toolcallready-re-confirmation-denied-transitions-to-cancelled.json @@ -1,7 +1,16 @@ { "description": "toolCallReady re-confirmation denied transitions to cancelled", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,35 +22,31 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run", "confirmed": "not-needed" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Permission needed" }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": false, @@ -49,6 +54,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -77,10 +91,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/029-toolcallready-ignores-non-streaming-non-running-tool-calls.json b/types/test-cases/reducers/029-toolcallready-ignores-non-streaming-non-running-tool-calls.json index c4d068f5..4f72e4f7 100644 --- a/types/test-cases/reducers/029-toolcallready-ignores-non-streaming-non-running-tool-calls.json +++ b/types/test-cases/reducers/029-toolcallready-ignores-non-streaming-non-running-tool-calls.json @@ -1,7 +1,16 @@ { "description": "toolCallReady ignores non-streaming/non-running tool calls", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -28,21 +37,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run again" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -69,10 +83,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/030-session-titlechanged-updates-title.json b/types/test-cases/reducers/030-session-titlechanged-updates-title.json index 9a8af04a..448d6306 100644 --- a/types/test-cases/reducers/030-session-titlechanged-updates-title.json +++ b/types/test-cases/reducers/030-session-titlechanged-updates-title.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -29,6 +29,6 @@ "modifiedAt": 9999 }, "lifecycle": "creating", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/031-session-usage-updates-usage-on-active-turn.json b/types/test-cases/reducers/031-session-usage-updates-usage-on-active-turn.json index c9cd4ec4..ef851304 100644 --- a/types/test-cases/reducers/031-session-usage-updates-usage-on-active-turn.json +++ b/types/test-cases/reducers/031-session-usage-updates-usage-on-active-turn.json @@ -1,7 +1,16 @@ { "description": "session/usage updates usage on active turn", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/usage", + "type": "session/usage", "turnId": "turn-1", "usage": { "inputTokens": 100, @@ -30,6 +35,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -44,10 +58,6 @@ "inputTokens": 100, "outputTokens": 50 } - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/032-session-reasoning-appends-reasoning-content.json b/types/test-cases/reducers/032-session-reasoning-appends-reasoning-content.json index 40338a9c..cd88f26d 100644 --- a/types/test-cases/reducers/032-session-reasoning-appends-reasoning-content.json +++ b/types/test-cases/reducers/032-session-reasoning-appends-reasoning-content.json @@ -1,7 +1,16 @@ { "description": "session/reasoning appends reasoning content", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/responsePart", + "type": "session/responsePart", "turnId": "turn-1", "part": { "kind": "reasoning", @@ -30,19 +35,28 @@ } }, { - "type": "chat/reasoning", + "type": "session/reasoning", "turnId": "turn-1", "partId": "r-1", "content": "Thinking about " }, { - "type": "chat/reasoning", + "type": "session/reasoning", "turnId": "turn-1", "partId": "r-1", "content": "the answer" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -60,10 +74,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/033-session-modelchanged-updates-model.json b/types/test-cases/reducers/033-session-modelchanged-updates-model.json index 61bf7a6e..650534b8 100644 --- a/types/test-cases/reducers/033-session-modelchanged-updates-model.json +++ b/types/test-cases/reducers/033-session-modelchanged-updates-model.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -34,6 +34,6 @@ } }, "lifecycle": "creating", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/034-session-servertoolschanged-sets-server-tools.json b/types/test-cases/reducers/034-session-servertoolschanged-sets-server-tools.json index 67919eed..b76d9be0 100644 --- a/types/test-cases/reducers/034-session-servertoolschanged-sets-server-tools.json +++ b/types/test-cases/reducers/034-session-servertoolschanged-sets-server-tools.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -34,12 +34,12 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "serverTools": [ { "name": "bash", "description": "Run shell commands" } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/035-session-activeclientchanged-sets-client.json b/types/test-cases/reducers/035-session-activeclientchanged-sets-client.json index 640d02a3..fa8b4b9d 100644 --- a/types/test-cases/reducers/035-session-activeclientchanged-sets-client.json +++ b/types/test-cases/reducers/035-session-activeclientchanged-sets-client.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -33,11 +33,11 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "activeClient": { "clientId": "vscode-1", "displayName": "VS Code", "tools": [] - }, - "chats": [] + } } } diff --git a/types/test-cases/reducers/036-session-activeclientchanged-unsets-client.json b/types/test-cases/reducers/036-session-activeclientchanged-unsets-client.json index 9d330118..8322a081 100644 --- a/types/test-cases/reducers/036-session-activeclientchanged-unsets-client.json +++ b/types/test-cases/reducers/036-session-activeclientchanged-unsets-client.json @@ -11,11 +11,11 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "activeClient": { "clientId": "vscode-1", "tools": [] - }, - "chats": [] + } }, "actions": [ { @@ -33,7 +33,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "activeClient": null, - "chats": [] + "turns": [], + "activeClient": null } } diff --git a/types/test-cases/reducers/037-session-activeclienttoolschanged-updates-tools.json b/types/test-cases/reducers/037-session-activeclienttoolschanged-updates-tools.json index c895cd60..e8542617 100644 --- a/types/test-cases/reducers/037-session-activeclienttoolschanged-updates-tools.json +++ b/types/test-cases/reducers/037-session-activeclienttoolschanged-updates-tools.json @@ -11,11 +11,11 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "activeClient": { "clientId": "vscode-1", "tools": [] - }, - "chats": [] + } }, "actions": [ { @@ -38,6 +38,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "activeClient": { "clientId": "vscode-1", "tools": [ @@ -46,7 +47,6 @@ "description": "Open a file" } ] - }, - "chats": [] + } } } diff --git a/types/test-cases/reducers/038-session-activeclienttoolschanged-without-activeclient-is-no-op.json b/types/test-cases/reducers/038-session-activeclienttoolschanged-without-activeclient-is-no-op.json index 8153b47e..7cd707c4 100644 --- a/types/test-cases/reducers/038-session-activeclienttoolschanged-without-activeclient-is-no-op.json +++ b/types/test-cases/reducers/038-session-activeclienttoolschanged-without-activeclient-is-no-op.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -33,6 +33,6 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/039-set-steering-message.json b/types/test-cases/reducers/039-set-steering-message.json index a5a1a1ef..5c588131 100644 --- a/types/test-cases/reducers/039-set-steering-message.json +++ b/types/test-cases/reducers/039-set-steering-message.json @@ -1,16 +1,21 @@ { "description": "set steering message", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] }, "actions": [ { - "type": "chat/pendingMessageSet", + "type": "session/pendingMessageSet", "kind": "steering", "id": "sm-1", "message": { @@ -22,6 +27,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "sm-1", @@ -31,10 +45,6 @@ "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/040-replace-existing-steering-message.json b/types/test-cases/reducers/040-replace-existing-steering-message.json index 380fadc2..44066d54 100644 --- a/types/test-cases/reducers/040-replace-existing-steering-message.json +++ b/types/test-cases/reducers/040-replace-existing-steering-message.json @@ -1,7 +1,16 @@ { "description": "replace existing steering message", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "sm-1", @@ -11,15 +20,11 @@ "kind": "user" } } - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + } }, "actions": [ { - "type": "chat/pendingMessageSet", + "type": "session/pendingMessageSet", "kind": "steering", "id": "sm-2", "message": { @@ -31,6 +36,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "sm-2", @@ -40,10 +54,6 @@ "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/041-update-steering-message-content-via-set-with-same-id.json b/types/test-cases/reducers/041-update-steering-message-content-via-set-with-same-id.json index a101a837..c0651dfb 100644 --- a/types/test-cases/reducers/041-update-steering-message-content-via-set-with-same-id.json +++ b/types/test-cases/reducers/041-update-steering-message-content-via-set-with-same-id.json @@ -1,7 +1,16 @@ { "description": "update steering message content via set with same ID", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "sm-1", @@ -11,15 +20,11 @@ "kind": "user" } } - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + } }, "actions": [ { - "type": "chat/pendingMessageSet", + "type": "session/pendingMessageSet", "kind": "steering", "id": "sm-1", "message": { @@ -31,6 +36,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "sm-1", @@ -40,10 +54,6 @@ "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/042-remove-steering-message.json b/types/test-cases/reducers/042-remove-steering-message.json index 7c4700e3..12f22c62 100644 --- a/types/test-cases/reducers/042-remove-steering-message.json +++ b/types/test-cases/reducers/042-remove-steering-message.json @@ -1,7 +1,16 @@ { "description": "remove steering message", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "sm-1", @@ -11,25 +20,26 @@ "kind": "user" } } - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + } }, "actions": [ { - "type": "chat/pendingMessageRemoved", + "type": "session/pendingMessageRemoved", "kind": "steering", "id": "sm-1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], - "steeringMessage": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "steeringMessage": null } } diff --git a/types/test-cases/reducers/043-remove-steering-message-is-no-op-for-mismatched-id.json b/types/test-cases/reducers/043-remove-steering-message-is-no-op-for-mismatched-id.json index 1e8ae5ca..fdf9e741 100644 --- a/types/test-cases/reducers/043-remove-steering-message-is-no-op-for-mismatched-id.json +++ b/types/test-cases/reducers/043-remove-steering-message-is-no-op-for-mismatched-id.json @@ -1,7 +1,16 @@ { "description": "remove steering message is no-op for mismatched ID", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "sm-1", @@ -11,20 +20,25 @@ "kind": "user" } } - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + } }, "actions": [ { - "type": "chat/pendingMessageRemoved", + "type": "session/pendingMessageRemoved", "kind": "steering", "id": "sm-unknown" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "sm-1", @@ -34,10 +48,6 @@ "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/044-remove-steering-message-is-no-op-when-none-exists.json b/types/test-cases/reducers/044-remove-steering-message-is-no-op-when-none-exists.json index 105b63ce..078caf1b 100644 --- a/types/test-cases/reducers/044-remove-steering-message-is-no-op-when-none-exists.json +++ b/types/test-cases/reducers/044-remove-steering-message-is-no-op-when-none-exists.json @@ -1,25 +1,35 @@ { "description": "remove steering message is no-op when none exists", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] }, "actions": [ { - "type": "chat/pendingMessageRemoved", + "type": "session/pendingMessageRemoved", "kind": "steering", "id": "sm-1" } ], "expected": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] } } diff --git a/types/test-cases/reducers/045-set-a-new-queued-message.json b/types/test-cases/reducers/045-set-a-new-queued-message.json index 27c33b98..844ba532 100644 --- a/types/test-cases/reducers/045-set-a-new-queued-message.json +++ b/types/test-cases/reducers/045-set-a-new-queued-message.json @@ -1,16 +1,21 @@ { "description": "set a new queued message", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] }, "actions": [ { - "type": "chat/pendingMessageSet", + "type": "session/pendingMessageSet", "kind": "queued", "id": "pm-1", "message": { @@ -22,6 +27,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -33,10 +47,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/046-append-queued-message-when-id-is-new.json b/types/test-cases/reducers/046-append-queued-message-when-id-is-new.json index 1d1fc70b..7a108cdc 100644 --- a/types/test-cases/reducers/046-append-queued-message-when-id-is-new.json +++ b/types/test-cases/reducers/046-append-queued-message-when-id-is-new.json @@ -1,7 +1,16 @@ { "description": "append queued message when ID is new", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -13,15 +22,11 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/pendingMessageSet", + "type": "session/pendingMessageSet", "kind": "queued", "id": "pm-2", "message": { @@ -33,6 +38,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -53,10 +67,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/047-update-queued-message-in-place-when-id-already-exists.json b/types/test-cases/reducers/047-update-queued-message-in-place-when-id-already-exists.json index d4f7aeeb..802d666d 100644 --- a/types/test-cases/reducers/047-update-queued-message-in-place-when-id-already-exists.json +++ b/types/test-cases/reducers/047-update-queued-message-in-place-when-id-already-exists.json @@ -1,7 +1,16 @@ { "description": "update queued message in place when ID already exists", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -22,15 +31,11 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/pendingMessageSet", + "type": "session/pendingMessageSet", "kind": "queued", "id": "pm-1", "message": { @@ -42,6 +47,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -62,10 +76,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/048-remove-a-queued-message.json b/types/test-cases/reducers/048-remove-a-queued-message.json index ee5aac54..0f089fec 100644 --- a/types/test-cases/reducers/048-remove-a-queued-message.json +++ b/types/test-cases/reducers/048-remove-a-queued-message.json @@ -1,7 +1,16 @@ { "description": "remove a queued message", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -22,20 +31,25 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/pendingMessageRemoved", + "type": "session/pendingMessageRemoved", "kind": "queued", "id": "pm-1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -47,10 +61,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/049-remove-last-queued-message-sets-array-to-undefined.json b/types/test-cases/reducers/049-remove-last-queued-message-sets-array-to-undefined.json index 17da3465..ef0ae69e 100644 --- a/types/test-cases/reducers/049-remove-last-queued-message-sets-array-to-undefined.json +++ b/types/test-cases/reducers/049-remove-last-queued-message-sets-array-to-undefined.json @@ -1,7 +1,16 @@ { "description": "remove last queued message sets array to undefined", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -13,25 +22,26 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/pendingMessageRemoved", + "type": "session/pendingMessageRemoved", "kind": "queued", "id": "pm-1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], - "queuedMessages": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "queuedMessages": null } } diff --git a/types/test-cases/reducers/050-remove-queued-message-is-no-op-for-unknown-id.json b/types/test-cases/reducers/050-remove-queued-message-is-no-op-for-unknown-id.json index dd366545..fea82c7f 100644 --- a/types/test-cases/reducers/050-remove-queued-message-is-no-op-for-unknown-id.json +++ b/types/test-cases/reducers/050-remove-queued-message-is-no-op-for-unknown-id.json @@ -1,7 +1,16 @@ { "description": "remove queued message is no-op for unknown ID", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -13,20 +22,25 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/pendingMessageRemoved", + "type": "session/pendingMessageRemoved", "kind": "queued", "id": "pm-unknown" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -38,10 +52,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/051-remove-queued-message-is-no-op-when-array-is-empty.json b/types/test-cases/reducers/051-remove-queued-message-is-no-op-when-array-is-empty.json index f29a1478..4018812b 100644 --- a/types/test-cases/reducers/051-remove-queued-message-is-no-op-when-array-is-empty.json +++ b/types/test-cases/reducers/051-remove-queued-message-is-no-op-when-array-is-empty.json @@ -1,25 +1,35 @@ { "description": "remove queued message is no-op when array is empty", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] }, "actions": [ { - "type": "chat/pendingMessageRemoved", + "type": "session/pendingMessageRemoved", "kind": "queued", "id": "pm-1" } ], "expected": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] } } diff --git a/types/test-cases/reducers/052-steering-and-queued-messages-are-independent.json b/types/test-cases/reducers/052-steering-and-queued-messages-are-independent.json index ca4fbdf7..5986b9b4 100644 --- a/types/test-cases/reducers/052-steering-and-queued-messages-are-independent.json +++ b/types/test-cases/reducers/052-steering-and-queued-messages-are-independent.json @@ -1,16 +1,21 @@ { "description": "steering and queued messages are independent", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] }, "actions": [ { - "type": "chat/pendingMessageSet", + "type": "session/pendingMessageSet", "kind": "steering", "id": "s-1", "message": { @@ -21,7 +26,7 @@ } }, { - "type": "chat/pendingMessageSet", + "type": "session/pendingMessageSet", "kind": "queued", "id": "q-1", "message": { @@ -33,6 +38,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "steeringMessage": { "id": "s-1", @@ -53,10 +67,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/053-reorders-queued-messages.json b/types/test-cases/reducers/053-reorders-queued-messages.json index 3f388de7..fdfbb362 100644 --- a/types/test-cases/reducers/053-reorders-queued-messages.json +++ b/types/test-cases/reducers/053-reorders-queued-messages.json @@ -1,7 +1,16 @@ { "description": "reorders queued messages", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -31,15 +40,11 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/queuedMessagesReordered", + "type": "session/queuedMessagesReordered", "order": [ "c", "a", @@ -48,6 +53,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -77,10 +91,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/054-keeps-messages-not-in-order-at-end-in-original-order.json b/types/test-cases/reducers/054-keeps-messages-not-in-order-at-end-in-original-order.json index c02b328a..a1bc7466 100644 --- a/types/test-cases/reducers/054-keeps-messages-not-in-order-at-end-in-original-order.json +++ b/types/test-cases/reducers/054-keeps-messages-not-in-order-at-end-in-original-order.json @@ -1,7 +1,16 @@ { "description": "keeps messages not in order at end in original order", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -31,21 +40,26 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/queuedMessagesReordered", + "type": "session/queuedMessagesReordered", "order": [ "c" ] } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -75,10 +89,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/055-ignores-unknown-ids-in-reorder.json b/types/test-cases/reducers/055-ignores-unknown-ids-in-reorder.json index 68f91aec..fdb2dca1 100644 --- a/types/test-cases/reducers/055-ignores-unknown-ids-in-reorder.json +++ b/types/test-cases/reducers/055-ignores-unknown-ids-in-reorder.json @@ -1,7 +1,16 @@ { "description": "ignores unknown IDs in reorder", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -22,15 +31,11 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/queuedMessagesReordered", + "type": "session/queuedMessagesReordered", "order": [ "unknown", "b", @@ -40,6 +45,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -60,10 +74,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/056-empty-reorder-preserves-all-messages-in-original-order.json b/types/test-cases/reducers/056-empty-reorder-preserves-all-messages-in-original-order.json index 4530500a..30a400ea 100644 --- a/types/test-cases/reducers/056-empty-reorder-preserves-all-messages-in-original-order.json +++ b/types/test-cases/reducers/056-empty-reorder-preserves-all-messages-in-original-order.json @@ -1,7 +1,16 @@ { "description": "empty reorder preserves all messages in original order", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -13,19 +22,24 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/queuedMessagesReordered", + "type": "session/queuedMessagesReordered", "order": [] } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", "turns": [], "queuedMessages": [ { @@ -37,10 +51,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/057-reorder-is-no-op-when-no-queued-messages-exist.json b/types/test-cases/reducers/057-reorder-is-no-op-when-no-queued-messages-exist.json index fadc8e3a..17d7517d 100644 --- a/types/test-cases/reducers/057-reorder-is-no-op-when-no-queued-messages-exist.json +++ b/types/test-cases/reducers/057-reorder-is-no-op-when-no-queued-messages-exist.json @@ -1,16 +1,21 @@ { "description": "reorder is no-op when no queued messages exist", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] }, "actions": [ { - "type": "chat/queuedMessagesReordered", + "type": "session/queuedMessagesReordered", "order": [ "a", "b" @@ -18,10 +23,15 @@ } ], "expected": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "creating", + "turns": [] } } diff --git a/types/test-cases/reducers/058-session-customizationschanged-replaces-entire-list.json b/types/test-cases/reducers/058-session-customizationschanged-replaces-entire-list.json index 77509bbb..ca18fa17 100644 --- a/types/test-cases/reducers/058-session-customizationschanged-replaces-entire-list.json +++ b/types/test-cases/reducers/058-session-customizationschanged-replaces-entire-list.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -45,6 +45,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -61,7 +62,6 @@ "enabled": false, "clientId": "client-1" } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/059-session-customizationschanged-replaces-existing-customizations.json b/types/test-cases/reducers/059-session-customizationschanged-replaces-existing-customizations.json index 0b45b67e..73e8fdd3 100644 --- a/types/test-cases/reducers/059-session-customizationschanged-replaces-existing-customizations.json +++ b/types/test-cases/reducers/059-session-customizationschanged-replaces-existing-customizations.json @@ -11,6 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -19,8 +20,7 @@ "name": "Plugin A", "enabled": true } - ], - "chats": [] + ] }, "actions": [ { @@ -46,6 +46,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -54,7 +55,6 @@ "name": "Plugin B", "enabled": false } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/060-session-customizationtoggled-toggles-by-id.json b/types/test-cases/reducers/060-session-customizationtoggled-toggles-by-id.json index 43904b20..1470f328 100644 --- a/types/test-cases/reducers/060-session-customizationtoggled-toggles-by-id.json +++ b/types/test-cases/reducers/060-session-customizationtoggled-toggles-by-id.json @@ -11,6 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -26,8 +27,7 @@ "name": "Plugin B", "enabled": true } - ], - "chats": [] + ] }, "actions": [ { @@ -46,6 +46,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -61,7 +62,6 @@ "name": "Plugin B", "enabled": true } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/061-session-customizationtoggled-is-no-op-for-unknown-id.json b/types/test-cases/reducers/061-session-customizationtoggled-is-no-op-for-unknown-id.json index 3df3cc84..8aad7c6c 100644 --- a/types/test-cases/reducers/061-session-customizationtoggled-is-no-op-for-unknown-id.json +++ b/types/test-cases/reducers/061-session-customizationtoggled-is-no-op-for-unknown-id.json @@ -11,6 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -19,8 +20,7 @@ "name": "Plugin A", "enabled": true } - ], - "chats": [] + ] }, "actions": [ { @@ -39,6 +39,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -47,7 +48,6 @@ "name": "Plugin A", "enabled": true } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/062-session-customizationtoggled-is-no-op-when-customizations-undefined.json b/types/test-cases/reducers/062-session-customizationtoggled-is-no-op-when-customizations-undefined.json index 630a0805..f5acdccb 100644 --- a/types/test-cases/reducers/062-session-customizationtoggled-is-no-op-when-customizations-undefined.json +++ b/types/test-cases/reducers/062-session-customizationtoggled-is-no-op-when-customizations-undefined.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -30,6 +30,6 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/063-session-truncated-keeps-turns-up-to-turnid.json b/types/test-cases/reducers/063-session-truncated-keeps-turns-up-to-turnid.json index e744a542..4bad1ba0 100644 --- a/types/test-cases/reducers/063-session-truncated-keeps-turns-up-to-turnid.json +++ b/types/test-cases/reducers/063-session-truncated-keeps-turns-up-to-turnid.json @@ -1,7 +1,16 @@ { "description": "session/truncated keeps turns up to turnId", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -39,19 +48,24 @@ "usage": null, "state": "complete" } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/truncated", + "type": "session/truncated", "turnId": "t2" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -78,10 +92,6 @@ "state": "complete" } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/064-session-truncated-keeps-all-when-turnid-is-last.json b/types/test-cases/reducers/064-session-truncated-keeps-all-when-turnid-is-last.json index edc7a4da..7e13b650 100644 --- a/types/test-cases/reducers/064-session-truncated-keeps-all-when-turnid-is-last.json +++ b/types/test-cases/reducers/064-session-truncated-keeps-all-when-turnid-is-last.json @@ -1,7 +1,16 @@ { "description": "session/truncated keeps all when turnId is last", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -27,19 +36,24 @@ "usage": null, "state": "complete" } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/truncated", + "type": "session/truncated", "turnId": "t2" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -66,10 +80,6 @@ "state": "complete" } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/065-session-truncated-keeps-only-first-when-turnid-is-first.json b/types/test-cases/reducers/065-session-truncated-keeps-only-first-when-turnid-is-first.json index 9d7bb9e1..4ad6df4b 100644 --- a/types/test-cases/reducers/065-session-truncated-keeps-only-first-when-turnid-is-first.json +++ b/types/test-cases/reducers/065-session-truncated-keeps-only-first-when-turnid-is-first.json @@ -1,7 +1,16 @@ { "description": "session/truncated keeps only first when turnId is first", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -39,19 +48,24 @@ "usage": null, "state": "complete" } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/truncated", + "type": "session/truncated", "turnId": "t1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -66,10 +80,6 @@ "state": "complete" } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/066-session-truncated-clears-all-when-turnid-omitted.json b/types/test-cases/reducers/066-session-truncated-clears-all-when-turnid-omitted.json index c27c0cfc..4514e893 100644 --- a/types/test-cases/reducers/066-session-truncated-clears-all-when-turnid-omitted.json +++ b/types/test-cases/reducers/066-session-truncated-clears-all-when-turnid-omitted.json @@ -1,7 +1,16 @@ { "description": "session/truncated clears all when turnId omitted", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -39,23 +48,24 @@ "usage": null, "state": "complete" } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/truncated" + "type": "session/truncated" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/067-session-truncated-drops-active-turn.json b/types/test-cases/reducers/067-session-truncated-drops-active-turn.json index d5dde94f..ea058dc6 100644 --- a/types/test-cases/reducers/067-session-truncated-drops-active-turn.json +++ b/types/test-cases/reducers/067-session-truncated-drops-active-turn.json @@ -1,7 +1,16 @@ { "description": "session/truncated drops active turn", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -38,19 +47,24 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/truncated", + "type": "session/truncated", "turnId": "t1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -65,10 +79,6 @@ "state": "complete" } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/068-session-truncated-drops-active-turn-even-when-clearing-all.json b/types/test-cases/reducers/068-session-truncated-drops-active-turn-even-when-clearing-all.json index e9435f3c..2738add3 100644 --- a/types/test-cases/reducers/068-session-truncated-drops-active-turn-even-when-clearing-all.json +++ b/types/test-cases/reducers/068-session-truncated-drops-active-turn-even-when-clearing-all.json @@ -1,7 +1,16 @@ { "description": "session/truncated drops active turn even when clearing all", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,23 +22,24 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/truncated" + "type": "session/truncated" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/069-session-truncated-is-no-op-for-unknown-turnid.json b/types/test-cases/reducers/069-session-truncated-is-no-op-for-unknown-turnid.json index 19a10953..103ec926 100644 --- a/types/test-cases/reducers/069-session-truncated-is-no-op-for-unknown-turnid.json +++ b/types/test-cases/reducers/069-session-truncated-is-no-op-for-unknown-turnid.json @@ -1,7 +1,16 @@ { "description": "session/truncated is no-op for unknown turnId", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -27,19 +36,24 @@ "usage": null, "state": "complete" } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] }, "actions": [ { - "type": "chat/truncated", + "type": "session/truncated", "turnId": "unknown" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -65,10 +79,6 @@ "usage": null, "state": "complete" } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + ] } } diff --git a/types/test-cases/reducers/070-full-turn-flow-with-tool-calls-and-re-confirmation.json b/types/test-cases/reducers/070-full-turn-flow-with-tool-calls-and-re-confirmation.json index e0420051..7fd6ebde 100644 --- a/types/test-cases/reducers/070-full-turn-flow-with-tool-calls-and-re-confirmation.json +++ b/types/test-cases/reducers/070-full-turn-flow-with-tool-calls-and-re-confirmation.json @@ -1,16 +1,21 @@ { "description": "full turn flow with tool calls and re-confirmation", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [] }, "actions": [ { - "type": "chat/turnStarted", + "type": "session/turnStarted", "turnId": "t1", "message": { "text": "Fix the bug", @@ -20,7 +25,7 @@ } }, { - "type": "chat/responsePart", + "type": "session/responsePart", "turnId": "t1", "part": { "kind": "markdown", @@ -29,33 +34,33 @@ } }, { - "type": "chat/delta", + "type": "session/delta", "turnId": "t1", "partId": "md-1", "content": "I will " }, { - "type": "chat/delta", + "type": "session/delta", "turnId": "t1", "partId": "md-1", "content": "fix it." }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "t1", "toolCallId": "tc1", "toolName": "edit", "displayName": "Edit File" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "t1", "toolCallId": "tc1", "invocationMessage": "Edit main.ts", "confirmed": "not-needed" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "t1", "toolCallId": "tc1", "result": { @@ -64,21 +69,21 @@ } }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "t1", "toolCallId": "tc2", "toolName": "write", "displayName": "Write File" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "t1", "toolCallId": "tc2", "invocationMessage": "Write /tmp/out", "confirmed": "not-needed" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "t1", "toolCallId": "tc2", "invocationMessage": "Write to /tmp/out", @@ -88,14 +93,14 @@ } }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "t1", "toolCallId": "tc2", "approved": true, "confirmed": "user-action" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "t1", "toolCallId": "tc2", "result": { @@ -104,7 +109,7 @@ } }, { - "type": "chat/usage", + "type": "session/usage", "turnId": "t1", "usage": { "inputTokens": 200, @@ -112,7 +117,7 @@ } }, { - "type": "chat/responsePart", + "type": "session/responsePart", "turnId": "t1", "part": { "kind": "reasoning", @@ -121,13 +126,13 @@ } }, { - "type": "chat/reasoning", + "type": "session/reasoning", "turnId": "t1", "partId": "r-1", "content": "The bug was in line 42" }, { - "type": "chat/responsePart", + "type": "session/responsePart", "turnId": "t1", "part": { "kind": "markdown", @@ -136,11 +141,20 @@ } }, { - "type": "chat/turnComplete", + "type": "session/turnComplete", "turnId": "t1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "t1", @@ -210,10 +224,6 @@ "error": null } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/071-session-isreadchanged-marks-session-as-read.json b/types/test-cases/reducers/071-session-isreadchanged-marks-session-as-read.json index 7d03fe34..d8f4aaa0 100644 --- a/types/test-cases/reducers/071-session-isreadchanged-marks-session-as-read.json +++ b/types/test-cases/reducers/071-session-isreadchanged-marks-session-as-read.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -29,6 +29,6 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/072-session-isreadchanged-marks-session-as-unread.json b/types/test-cases/reducers/072-session-isreadchanged-marks-session-as-unread.json index 94959691..3fd8fb44 100644 --- a/types/test-cases/reducers/072-session-isreadchanged-marks-session-as-unread.json +++ b/types/test-cases/reducers/072-session-isreadchanged-marks-session-as-unread.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -29,6 +29,6 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/073-session-isarchivedchanged-marks-session-as-archived.json b/types/test-cases/reducers/073-session-isarchivedchanged-marks-session-as-archived.json index 738e724a..9b1686bc 100644 --- a/types/test-cases/reducers/073-session-isarchivedchanged-marks-session-as-archived.json +++ b/types/test-cases/reducers/073-session-isarchivedchanged-marks-session-as-archived.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -29,6 +29,6 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/074-session-isarchivedchanged-unarchives-session.json b/types/test-cases/reducers/074-session-isarchivedchanged-unarchives-session.json index 299a9df6..f068c356 100644 --- a/types/test-cases/reducers/074-session-isarchivedchanged-unarchives-session.json +++ b/types/test-cases/reducers/074-session-isarchivedchanged-unarchives-session.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -29,6 +29,6 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/075-turnstarted-clears-isread.json b/types/test-cases/reducers/075-turnstarted-clears-isread.json index 80005421..5bda987f 100644 --- a/types/test-cases/reducers/075-turnstarted-clears-isread.json +++ b/types/test-cases/reducers/075-turnstarted-clears-isread.json @@ -1,16 +1,21 @@ { "description": "session/turnStarted clears isRead on a previously-read session", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 33, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 33, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [] }, "actions": [ { - "type": "chat/turnStarted", + "type": "session/turnStarted", "turnId": "turn-1", "message": { "text": "Hello", @@ -21,6 +26,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -32,10 +46,6 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:09.999Z" + } } } diff --git a/types/test-cases/reducers/084-toolcall-contentchanged-updates-running.json b/types/test-cases/reducers/084-toolcall-contentchanged-updates-running.json index 1ae61075..43ffb35e 100644 --- a/types/test-cases/reducers/084-toolcall-contentchanged-updates-running.json +++ b/types/test-cases/reducers/084-toolcall-contentchanged-updates-running.json @@ -1,7 +1,16 @@ { "description": "toolCallContentChanged sets content on a running tool call", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -26,15 +35,11 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallContentChanged", + "type": "session/toolCallContentChanged", "turnId": "turn-1", "toolCallId": "tc-1", "content": [ @@ -47,6 +52,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -78,10 +92,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/085-toolcall-contentchanged-noop-non-running.json b/types/test-cases/reducers/085-toolcall-contentchanged-noop-non-running.json index 6fbfc1ef..980ec220 100644 --- a/types/test-cases/reducers/085-toolcall-contentchanged-noop-non-running.json +++ b/types/test-cases/reducers/085-toolcall-contentchanged-noop-non-running.json @@ -1,7 +1,16 @@ { "description": "toolCallContentChanged is no-op for non-running tool call", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -28,15 +37,11 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallContentChanged", + "type": "session/toolCallContentChanged", "turnId": "turn-1", "toolCallId": "tc-1", "content": [ @@ -49,6 +54,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -75,10 +89,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/086-toolcall-contentchanged-replaces-existing.json b/types/test-cases/reducers/086-toolcall-contentchanged-replaces-existing.json index ff3acddb..42c18e5e 100644 --- a/types/test-cases/reducers/086-toolcall-contentchanged-replaces-existing.json +++ b/types/test-cases/reducers/086-toolcall-contentchanged-replaces-existing.json @@ -1,7 +1,16 @@ { "description": "toolCallContentChanged replaces existing content on a running tool call", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -33,15 +42,11 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallContentChanged", + "type": "session/toolCallContentChanged", "turnId": "turn-1", "toolCallId": "tc-1", "content": [ @@ -58,6 +63,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -93,10 +107,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/088-session-unknown-action-type-is-no-op.json b/types/test-cases/reducers/088-session-unknown-action-type-is-no-op.json index c12d9f4f..478e2f1b 100644 --- a/types/test-cases/reducers/088-session-unknown-action-type-is-no-op.json +++ b/types/test-cases/reducers/088-session-unknown-action-type-is-no-op.json @@ -11,7 +11,7 @@ "modifiedAt": 2000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -28,6 +28,6 @@ "modifiedAt": 2000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/090-toolcalldelta-wrong-turnid-is-no-op.json b/types/test-cases/reducers/090-toolcalldelta-wrong-turnid-is-no-op.json index 6f4fca22..70c89e50 100644 --- a/types/test-cases/reducers/090-toolcalldelta-wrong-turnid-is-no-op.json +++ b/types/test-cases/reducers/090-toolcalldelta-wrong-turnid-is-no-op.json @@ -1,7 +1,16 @@ { "description": "toolCallDelta with wrong turnId is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -23,21 +32,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallDelta", + "type": "session/toolCallDelta", "turnId": "wrong-turn", "toolCallId": "tc-1", "content": "data" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -59,10 +73,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/091-responsepart-wrong-turnid-is-no-op.json b/types/test-cases/reducers/091-responsepart-wrong-turnid-is-no-op.json index db7e3c87..3a7120d7 100644 --- a/types/test-cases/reducers/091-responsepart-wrong-turnid-is-no-op.json +++ b/types/test-cases/reducers/091-responsepart-wrong-turnid-is-no-op.json @@ -1,7 +1,16 @@ { "description": "responsePart with wrong turnId is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/responsePart", + "type": "session/responsePart", "turnId": "wrong-turn", "part": { "kind": "markdown", @@ -31,6 +36,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -42,10 +56,6 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/092-toolcallstart-wrong-turnid-is-no-op.json b/types/test-cases/reducers/092-toolcallstart-wrong-turnid-is-no-op.json index 64d7f700..5274894d 100644 --- a/types/test-cases/reducers/092-toolcallstart-wrong-turnid-is-no-op.json +++ b/types/test-cases/reducers/092-toolcallstart-wrong-turnid-is-no-op.json @@ -1,7 +1,16 @@ { "description": "toolCallStart with wrong turnId is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "wrong-turn", "toolCallId": "tc-1", "toolName": "bash", @@ -29,6 +34,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -40,10 +54,6 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/093-usage-wrong-turnid-is-no-op.json b/types/test-cases/reducers/093-usage-wrong-turnid-is-no-op.json index cb26096f..11ff8788 100644 --- a/types/test-cases/reducers/093-usage-wrong-turnid-is-no-op.json +++ b/types/test-cases/reducers/093-usage-wrong-turnid-is-no-op.json @@ -1,7 +1,16 @@ { "description": "usage with wrong turnId is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/usage", + "type": "session/usage", "turnId": "wrong-turn", "usage": { "inputTokens": 100, @@ -30,6 +35,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -41,10 +55,6 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/094-delta-nonexistent-partid-is-no-op.json b/types/test-cases/reducers/094-delta-nonexistent-partid-is-no-op.json index 7c564a93..dd0ba386 100644 --- a/types/test-cases/reducers/094-delta-nonexistent-partid-is-no-op.json +++ b/types/test-cases/reducers/094-delta-nonexistent-partid-is-no-op.json @@ -1,7 +1,16 @@ { "description": "delta with non-existent partId is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -19,21 +28,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/delta", + "type": "session/delta", "turnId": "turn-1", "partId": "nonexistent", "content": "data" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -51,10 +65,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/095-toolcalldelta-wrong-status-is-no-op.json b/types/test-cases/reducers/095-toolcalldelta-wrong-status-is-no-op.json index d24f93ab..8f318949 100644 --- a/types/test-cases/reducers/095-toolcalldelta-wrong-status-is-no-op.json +++ b/types/test-cases/reducers/095-toolcalldelta-wrong-status-is-no-op.json @@ -1,7 +1,16 @@ { "description": "toolCallDelta on non-streaming tool call is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -26,21 +35,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallDelta", + "type": "session/toolCallDelta", "turnId": "turn-1", "toolCallId": "tc-1", "content": "extra data" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -65,10 +79,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/096-toolcallconfirmed-wrong-status-is-no-op.json b/types/test-cases/reducers/096-toolcallconfirmed-wrong-status-is-no-op.json index bca47e6a..2928d571 100644 --- a/types/test-cases/reducers/096-toolcallconfirmed-wrong-status-is-no-op.json +++ b/types/test-cases/reducers/096-toolcallconfirmed-wrong-status-is-no-op.json @@ -1,7 +1,16 @@ { "description": "toolCallConfirmed on non-pending tool call is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -26,15 +35,11 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true, @@ -42,6 +47,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -66,10 +80,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/097-toolcallcomplete-wrong-status-is-no-op.json b/types/test-cases/reducers/097-toolcallcomplete-wrong-status-is-no-op.json index e0a00a7f..dc2c8a7e 100644 --- a/types/test-cases/reducers/097-toolcallcomplete-wrong-status-is-no-op.json +++ b/types/test-cases/reducers/097-toolcallcomplete-wrong-status-is-no-op.json @@ -1,7 +1,16 @@ { "description": "toolCallComplete on non-running non-pending tool call is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -28,15 +37,11 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -46,6 +51,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -72,10 +86,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/098-toolcallresultconfirmed-wrong-status-is-no-op.json b/types/test-cases/reducers/098-toolcallresultconfirmed-wrong-status-is-no-op.json index 30669340..05a9e527 100644 --- a/types/test-cases/reducers/098-toolcallresultconfirmed-wrong-status-is-no-op.json +++ b/types/test-cases/reducers/098-toolcallresultconfirmed-wrong-status-is-no-op.json @@ -1,7 +1,16 @@ { "description": "toolCallResultConfirmed on non-pending-result tool call is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -26,21 +35,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallResultConfirmed", + "type": "session/toolCallResultConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -65,10 +79,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/099-endturn-force-cancels-running-tool-call.json b/types/test-cases/reducers/099-endturn-force-cancels-running-tool-call.json index 078187c4..9cfceeb6 100644 --- a/types/test-cases/reducers/099-endturn-force-cancels-running-tool-call.json +++ b/types/test-cases/reducers/099-endturn-force-cancels-running-tool-call.json @@ -1,7 +1,16 @@ { "description": "endTurn force-cancels running tool call with non-streaming fields", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -26,19 +35,24 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/turnComplete", + "type": "session/turnComplete", "turnId": "turn-1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "turn-1", @@ -69,10 +83,6 @@ "error": null } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/100-delta-targeting-toolcall-partid-is-no-op.json b/types/test-cases/reducers/100-delta-targeting-toolcall-partid-is-no-op.json index 79b2e8d6..bb6f9f1c 100644 --- a/types/test-cases/reducers/100-delta-targeting-toolcall-partid-is-no-op.json +++ b/types/test-cases/reducers/100-delta-targeting-toolcall-partid-is-no-op.json @@ -1,7 +1,16 @@ { "description": "delta targeting a tool call partId is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -26,21 +35,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/delta", + "type": "session/delta", "turnId": "turn-1", "partId": "tc-1", "content": "data" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -65,10 +79,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/101-toolcalldelta-without-invocationmessage.json b/types/test-cases/reducers/101-toolcalldelta-without-invocationmessage.json index ce34f7e2..49d49308 100644 --- a/types/test-cases/reducers/101-toolcalldelta-without-invocationmessage.json +++ b/types/test-cases/reducers/101-toolcalldelta-without-invocationmessage.json @@ -1,7 +1,16 @@ { "description": "toolCallDelta without invocationMessage preserves existing", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -24,21 +33,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallDelta", + "type": "session/toolCallDelta", "turnId": "turn-1", "toolCallId": "tc-1", "content": "data" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -62,10 +76,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/102-reasoning-targeting-non-reasoning-is-no-op.json b/types/test-cases/reducers/102-reasoning-targeting-non-reasoning-is-no-op.json index f2121787..538893f6 100644 --- a/types/test-cases/reducers/102-reasoning-targeting-non-reasoning-is-no-op.json +++ b/types/test-cases/reducers/102-reasoning-targeting-non-reasoning-is-no-op.json @@ -1,7 +1,16 @@ { "description": "reasoning targeting non-reasoning part is no-op", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -19,21 +28,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/reasoning", + "type": "session/reasoning", "turnId": "turn-1", "partId": "md-1", "content": "thinking..." } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -51,10 +65,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/103-delta-skips-parts-without-id.json b/types/test-cases/reducers/103-delta-skips-parts-without-id.json index c205564e..0cbb7bc8 100644 --- a/types/test-cases/reducers/103-delta-skips-parts-without-id.json +++ b/types/test-cases/reducers/103-delta-skips-parts-without-id.json @@ -1,7 +1,16 @@ { "description": "delta skips response parts without id field", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -22,21 +31,26 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/delta", + "type": "session/delta", "turnId": "turn-1", "partId": "md-1", "content": " new" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -57,10 +71,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/105-session-input-full-draft-and-complete-flow.json b/types/test-cases/reducers/105-session-input-full-draft-and-complete-flow.json index d0e5be92..42c72946 100644 --- a/types/test-cases/reducers/105-session-input-full-draft-and-complete-flow.json +++ b/types/test-cases/reducers/105-session-input-full-draft-and-complete-flow.json @@ -1,16 +1,21 @@ { "description": "session input requests sync drafts and restore in-progress status after completion", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [] }, "actions": [ { - "type": "chat/turnStarted", + "type": "session/turnStarted", "turnId": "turn-1", "message": { "text": "Hello", @@ -20,7 +25,7 @@ } }, { - "type": "chat/inputRequested", + "type": "session/inputRequested", "request": { "id": "input-1", "message": "Clarify requirements", @@ -49,7 +54,7 @@ } }, { - "type": "chat/inputAnswerChanged", + "type": "session/inputAnswerChanged", "requestId": "input-1", "questionId": "q1", "answer": { @@ -61,7 +66,7 @@ } }, { - "type": "chat/inputAnswerChanged", + "type": "session/inputAnswerChanged", "requestId": "input-1", "questionId": "q2", "answer": { @@ -73,12 +78,21 @@ } }, { - "type": "chat/inputCompleted", + "type": "session/inputCompleted", "requestId": "input-1", "response": "accept" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -90,10 +104,6 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:09.999Z" + } } } diff --git a/types/test-cases/reducers/106-session-input-requested-with-drafts-status.json b/types/test-cases/reducers/106-session-input-requested-with-drafts-status.json index bd66b38c..59fa3300 100644 --- a/types/test-cases/reducers/106-session-input-requested-with-drafts-status.json +++ b/types/test-cases/reducers/106-session-input-requested-with-drafts-status.json @@ -1,7 +1,16 @@ { "description": "session/inputRequested sets inputNeeded status and syncs draft answers", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/inputRequested", + "type": "session/inputRequested", "request": { "id": "input-1", "message": "Clarify requirements", @@ -35,7 +40,7 @@ } }, { - "type": "chat/inputAnswerChanged", + "type": "session/inputAnswerChanged", "requestId": "input-1", "questionId": "q1", "answer": { @@ -48,6 +53,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -81,10 +95,6 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:09.999Z" + ] } } diff --git a/types/test-cases/reducers/107-session-input-turn-end-cleans-turn-scoped-only.json b/types/test-cases/reducers/107-session-input-turn-end-cleans-turn-scoped-only.json index b7c5236c..10cddd87 100644 --- a/types/test-cases/reducers/107-session-input-turn-end-cleans-turn-scoped-only.json +++ b/types/test-cases/reducers/107-session-input-turn-end-cleans-turn-scoped-only.json @@ -1,7 +1,16 @@ { "description": "turn end clears outstanding session input requests", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -31,19 +40,24 @@ } ] } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + ] }, "actions": [ { - "type": "chat/turnComplete", + "type": "session/turnComplete", "turnId": "turn-1" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "turn-1", @@ -59,10 +73,6 @@ "error": null } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/108-session-input-upsert-and-clear-answer.json b/types/test-cases/reducers/108-session-input-upsert-and-clear-answer.json index 64523607..aa35e803 100644 --- a/types/test-cases/reducers/108-session-input-upsert-and-clear-answer.json +++ b/types/test-cases/reducers/108-session-input-upsert-and-clear-answer.json @@ -1,7 +1,16 @@ { "description": "session input upsert preserves or replaces answers and answerChanged clears last draft", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -28,22 +37,18 @@ } } } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + ] }, "actions": [ { - "type": "chat/inputRequested", + "type": "session/inputRequested", "request": { "id": "input-1", "message": "New message" } }, { - "type": "chat/inputRequested", + "type": "session/inputRequested", "request": { "id": "input-1", "message": "New message", @@ -55,12 +60,21 @@ } }, { - "type": "chat/inputAnswerChanged", + "type": "session/inputAnswerChanged", "requestId": "input-1", "questionId": "q2" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -79,10 +93,6 @@ "message": "New message", "answers": null } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:09.999Z" + ] } } diff --git a/types/test-cases/reducers/109-session-input-unknown-actions-are-no-op.json b/types/test-cases/reducers/109-session-input-unknown-actions-are-no-op.json index fa1d0dff..6b843b59 100644 --- a/types/test-cases/reducers/109-session-input-unknown-actions-are-no-op.json +++ b/types/test-cases/reducers/109-session-input-unknown-actions-are-no-op.json @@ -1,16 +1,21 @@ { "description": "session input answer and completion actions are no-op for unknown requests", - "reducer": "chat", + "reducer": "session", "initial": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [] }, "actions": [ { - "type": "chat/inputAnswerChanged", + "type": "session/inputAnswerChanged", "requestId": "missing", "questionId": "q1", "answer": { @@ -18,16 +23,21 @@ } }, { - "type": "chat/inputCompleted", + "type": "session/inputCompleted", "requestId": "missing", "response": "cancel" } ], "expected": { - "turns": [], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z" + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 1000 + }, + "lifecycle": "ready", + "turns": [] } } diff --git a/types/test-cases/reducers/110-session-input-completion-and-truncation-filtering.json b/types/test-cases/reducers/110-session-input-completion-and-truncation-filtering.json index 8987abf0..d6bb83c4 100644 --- a/types/test-cases/reducers/110-session-input-completion-and-truncation-filtering.json +++ b/types/test-cases/reducers/110-session-input-completion-and-truncation-filtering.json @@ -1,7 +1,16 @@ { "description": "session input completion and truncation remove outstanding requests", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [ { "id": "kept", @@ -49,24 +58,29 @@ "message": "Remaining request", "url": "https://example.com/form" } - ], - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + ] }, "actions": [ { - "type": "chat/inputCompleted", + "type": "session/inputCompleted", "requestId": "complete-me", "response": "decline" }, { - "type": "chat/truncated", + "type": "session/truncated", "turnId": "kept" } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 1, + "createdAt": 1000, + "modifiedAt": 9999 + }, + "lifecycle": "ready", "turns": [ { "id": "kept", @@ -81,10 +95,6 @@ "state": "complete" } ], - "activeTurn": null, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z" + "activeTurn": null } } diff --git a/types/test-cases/reducers/111-toolcall-pending-confirmation-sets-input-needed-status.json b/types/test-cases/reducers/111-toolcall-pending-confirmation-sets-input-needed-status.json index 26661966..85cc1863 100644 --- a/types/test-cases/reducers/111-toolcall-pending-confirmation-sets-input-needed-status.json +++ b/types/test-cases/reducers/111-toolcall-pending-confirmation-sets-input-needed-status.json @@ -1,7 +1,16 @@ { "description": "tool call awaiting confirmation sets session status to input-needed", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,22 +22,18 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: rm -rf /tmp/test", @@ -36,6 +41,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -64,10 +78,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/112-toolcall-pending-result-confirmation-sets-input-needed-status.json b/types/test-cases/reducers/112-toolcall-pending-result-confirmation-sets-input-needed-status.json index 36e2a30a..375f7512 100644 --- a/types/test-cases/reducers/112-toolcall-pending-result-confirmation-sets-input-needed-status.json +++ b/types/test-cases/reducers/112-toolcall-pending-result-confirmation-sets-input-needed-status.json @@ -1,7 +1,16 @@ { "description": "tool call pending result confirmation sets session status to input-needed", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,29 +22,25 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run", "confirmed": "not-needed" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -46,6 +51,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -74,10 +88,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/113-session-configchanged-merges-into-config-values.json b/types/test-cases/reducers/113-session-configchanged-merges-into-config-values.json index 23e47469..63f46c0a 100644 --- a/types/test-cases/reducers/113-session-configchanged-merges-into-config-values.json +++ b/types/test-cases/reducers/113-session-configchanged-merges-into-config-values.json @@ -38,7 +38,7 @@ } }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -85,6 +85,6 @@ } }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/114-session-configchanged-noops-when-config-undefined.json b/types/test-cases/reducers/114-session-configchanged-noops-when-config-undefined.json index 412f0d6c..19b4c27c 100644 --- a/types/test-cases/reducers/114-session-configchanged-noops-when-config-undefined.json +++ b/types/test-cases/reducers/114-session-configchanged-noops-when-config-undefined.json @@ -11,7 +11,7 @@ "modifiedAt": 2000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -31,6 +31,6 @@ "modifiedAt": 2000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/126-session-modelchanged-with-modelconfig.json b/types/test-cases/reducers/126-session-modelchanged-with-modelconfig.json index 3b78b2d1..ddac6734 100644 --- a/types/test-cases/reducers/126-session-modelchanged-with-modelconfig.json +++ b/types/test-cases/reducers/126-session-modelchanged-with-modelconfig.json @@ -14,7 +14,7 @@ } }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -43,6 +43,6 @@ } }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/127-toolcallready-with-confirmation-options.json b/types/test-cases/reducers/127-toolcallready-with-confirmation-options.json index 786f8e0f..06aadb19 100644 --- a/types/test-cases/reducers/127-toolcallready-with-confirmation-options.json +++ b/types/test-cases/reducers/127-toolcallready-with-confirmation-options.json @@ -1,7 +1,16 @@ { "description": "toolCallReady with options preserves them in pending-confirmation state", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,22 +22,18 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: echo hello", @@ -61,6 +66,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -115,10 +129,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/128-toolcallconfirmed-approved-with-selectedoption.json b/types/test-cases/reducers/128-toolcallconfirmed-approved-with-selectedoption.json index 01ba9372..759ebd19 100644 --- a/types/test-cases/reducers/128-toolcallconfirmed-approved-with-selectedoption.json +++ b/types/test-cases/reducers/128-toolcallconfirmed-approved-with-selectedoption.json @@ -1,7 +1,16 @@ { "description": "toolCallConfirmed approved with selectedOptionId resolves selectedOption in running state", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,22 +22,18 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: echo hello", @@ -54,7 +59,7 @@ ] }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true, @@ -63,6 +68,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -95,10 +109,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/129-session-configchanged-replace-replaces-all-values.json b/types/test-cases/reducers/129-session-configchanged-replace-replaces-all-values.json index 29fc2907..a4120002 100644 --- a/types/test-cases/reducers/129-session-configchanged-replace-replaces-all-values.json +++ b/types/test-cases/reducers/129-session-configchanged-replace-replaces-all-values.json @@ -38,7 +38,7 @@ } }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -85,6 +85,6 @@ } }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/129-toolcallconfirmed-denied-with-selectedoption.json b/types/test-cases/reducers/129-toolcallconfirmed-denied-with-selectedoption.json index 1320e5ab..91d9eb76 100644 --- a/types/test-cases/reducers/129-toolcallconfirmed-denied-with-selectedoption.json +++ b/types/test-cases/reducers/129-toolcallconfirmed-denied-with-selectedoption.json @@ -1,7 +1,16 @@ { "description": "toolCallConfirmed denied with selectedOptionId resolves selectedOption in cancelled state", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,22 +22,18 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: rm -rf /", @@ -54,7 +59,7 @@ ] }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": false, @@ -64,6 +69,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -98,10 +112,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/130-selectedoption-carries-through-to-completed.json b/types/test-cases/reducers/130-selectedoption-carries-through-to-completed.json index 7f6938d9..e685e0ba 100644 --- a/types/test-cases/reducers/130-selectedoption-carries-through-to-completed.json +++ b/types/test-cases/reducers/130-selectedoption-carries-through-to-completed.json @@ -1,7 +1,16 @@ { "description": "selectedOption carries through from running to completed via toolCallComplete", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,22 +22,18 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: echo hello", @@ -48,7 +53,7 @@ ] }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true, @@ -56,7 +61,7 @@ "selectedOptionId": "approve-session" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -66,6 +71,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -100,10 +114,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/131-selectedoption-carries-through-result-confirmation.json b/types/test-cases/reducers/131-selectedoption-carries-through-result-confirmation.json index 63ea7420..cfeee2f1 100644 --- a/types/test-cases/reducers/131-selectedoption-carries-through-result-confirmation.json +++ b/types/test-cases/reducers/131-selectedoption-carries-through-result-confirmation.json @@ -1,7 +1,16 @@ { "description": "selectedOption carries through pending-result-confirmation approved to completed", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,22 +22,18 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: echo hello", @@ -42,7 +47,7 @@ ] }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true, @@ -50,7 +55,7 @@ "selectedOptionId": "approve-session" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -60,13 +65,22 @@ "requiresResultConfirmation": true }, { - "type": "chat/toolCallResultConfirmed", + "type": "session/toolCallResultConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -104,10 +118,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/132-selectedoption-carries-through-result-denied.json b/types/test-cases/reducers/132-selectedoption-carries-through-result-denied.json index 44be523e..7c7d9351 100644 --- a/types/test-cases/reducers/132-selectedoption-carries-through-result-denied.json +++ b/types/test-cases/reducers/132-selectedoption-carries-through-result-denied.json @@ -1,7 +1,16 @@ { "description": "selectedOption carries through result-denied cancellation", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,22 +22,18 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: echo hello", @@ -42,7 +47,7 @@ ] }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true, @@ -50,7 +55,7 @@ "selectedOptionId": "approve-once" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -60,13 +65,22 @@ "requiresResultConfirmation": true }, { - "type": "chat/toolCallResultConfirmed", + "type": "session/toolCallResultConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": false } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -99,10 +113,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/133-session-activitychanged-sets-activity.json b/types/test-cases/reducers/133-session-activitychanged-sets-activity.json index c1028260..6a95a2e4 100644 --- a/types/test-cases/reducers/133-session-activitychanged-sets-activity.json +++ b/types/test-cases/reducers/133-session-activitychanged-sets-activity.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -30,6 +30,6 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/134-session-activitychanged-clears-activity.json b/types/test-cases/reducers/134-session-activitychanged-clears-activity.json index 33b7c8d4..5981cbac 100644 --- a/types/test-cases/reducers/134-session-activitychanged-clears-activity.json +++ b/types/test-cases/reducers/134-session-activitychanged-clears-activity.json @@ -12,7 +12,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -31,6 +31,6 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/135-session-metachanged-sets-meta.json b/types/test-cases/reducers/135-session-metachanged-sets-meta.json index 6b2c4363..a6516c21 100644 --- a/types/test-cases/reducers/135-session-metachanged-sets-meta.json +++ b/types/test-cases/reducers/135-session-metachanged-sets-meta.json @@ -11,12 +11,12 @@ "modifiedAt": 1000 }, "lifecycle": "ready", + "turns": [], "_meta": { "git": { "branch": "main" } - }, - "chats": [] + } }, "actions": [ { @@ -39,12 +39,12 @@ "modifiedAt": 1000 }, "lifecycle": "ready", + "turns": [], "_meta": { "git": { "branch": "feature" }, "vscode.foo": 42 - }, - "chats": [] + } } } diff --git a/types/test-cases/reducers/137-session-customizationupdated-replaces-existing-container.json b/types/test-cases/reducers/137-session-customizationupdated-replaces-existing-container.json index 77e692dd..05df95ff 100644 --- a/types/test-cases/reducers/137-session-customizationupdated-replaces-existing-container.json +++ b/types/test-cases/reducers/137-session-customizationupdated-replaces-existing-container.json @@ -11,6 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -18,9 +19,7 @@ "uri": "https://plugins.example/a", "name": "Plugin A", "enabled": true, - "load": { - "kind": "loading" - } + "load": { "kind": "loading" } }, { "type": "plugin", @@ -28,12 +27,9 @@ "uri": "https://plugins.example/b", "name": "Plugin B", "enabled": true, - "load": { - "kind": "loaded" - } + "load": { "kind": "loaded" } } - ], - "chats": [] + ] }, "actions": [ { @@ -44,10 +40,7 @@ "uri": "https://plugins.example/a", "name": "Plugin A", "enabled": true, - "load": { - "kind": "error", - "message": "Failed to load" - } + "load": { "kind": "error", "message": "Failed to load" } } } ], @@ -61,6 +54,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -68,10 +62,7 @@ "uri": "https://plugins.example/a", "name": "Plugin A", "enabled": true, - "load": { - "kind": "error", - "message": "Failed to load" - } + "load": { "kind": "error", "message": "Failed to load" } }, { "type": "plugin", @@ -79,11 +70,8 @@ "uri": "https://plugins.example/b", "name": "Plugin B", "enabled": true, - "load": { - "kind": "loaded" - } + "load": { "kind": "loaded" } } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/138-session-customizationupdated-appends-unknown-id.json b/types/test-cases/reducers/138-session-customizationupdated-appends-unknown-id.json index a49ecd7c..378de3e6 100644 --- a/types/test-cases/reducers/138-session-customizationupdated-appends-unknown-id.json +++ b/types/test-cases/reducers/138-session-customizationupdated-appends-unknown-id.json @@ -11,6 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -19,8 +20,7 @@ "name": "Plugin A", "enabled": true } - ], - "chats": [] + ] }, "actions": [ { @@ -31,9 +31,7 @@ "uri": "https://plugins.example/new", "name": "New Plugin", "enabled": true, - "load": { - "kind": "loading" - } + "load": { "kind": "loading" } } } ], @@ -47,6 +45,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -61,11 +60,8 @@ "uri": "https://plugins.example/new", "name": "New Plugin", "enabled": true, - "load": { - "kind": "loading" - } + "load": { "kind": "loading" } } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/139-session-customizationupdated-creates-list.json b/types/test-cases/reducers/139-session-customizationupdated-creates-list.json index c17e124c..f537b0e5 100644 --- a/types/test-cases/reducers/139-session-customizationupdated-creates-list.json +++ b/types/test-cases/reducers/139-session-customizationupdated-creates-list.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -37,6 +37,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "directory", @@ -47,7 +48,6 @@ "contents": "skill", "writable": true } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json b/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json index 1592828f..57397945 100644 --- a/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json +++ b/types/test-cases/reducers/145-session-changesetschanged-sets-catalogue.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] }, "actions": [ { @@ -35,13 +35,13 @@ "modifiedAt": 1000 }, "lifecycle": "ready", + "turns": [], "changesets": [ { "label": "Session Changes", "uriTemplate": "copilot:/test-session/changeset/session", "changeKind": "session" } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json b/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json index 9c119c7c..f99a5804 100644 --- a/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json +++ b/types/test-cases/reducers/146-session-changesetschanged-clears-catalogue.json @@ -11,14 +11,14 @@ "modifiedAt": 1000 }, "lifecycle": "ready", + "turns": [], "changesets": [ { "label": "Session Changes", "uriTemplate": "copilot:/test-session/changeset/session", "changeKind": "session" } - ], - "chats": [] + ] }, "actions": [ { @@ -36,6 +36,6 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/147-session-agentchanged-sets-agent.json b/types/test-cases/reducers/147-session-agentchanged-sets-agent.json index f97f0d4f..bcb1b276 100644 --- a/types/test-cases/reducers/147-session-agentchanged-sets-agent.json +++ b/types/test-cases/reducers/147-session-agentchanged-sets-agent.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -34,6 +34,6 @@ } }, "lifecycle": "creating", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/147-session-ready-preserves-inprogress-status.json b/types/test-cases/reducers/147-session-ready-preserves-inprogress-status.json index da95e868..5685e4bd 100644 --- a/types/test-cases/reducers/147-session-ready-preserves-inprogress-status.json +++ b/types/test-cases/reducers/147-session-ready-preserves-inprogress-status.json @@ -11,7 +11,18 @@ "modifiedAt": 1000 }, "lifecycle": "creating", - "chats": [] + "turns": [], + "activeTurn": { + "id": "turn-1", + "message": { + "text": "Hello", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null + } }, "actions": [ { @@ -28,6 +39,17 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [] + "turns": [], + "activeTurn": { + "id": "turn-1", + "message": { + "text": "Hello", + "origin": { + "kind": "user" + } + }, + "responseParts": [], + "usage": null + } } } diff --git a/types/test-cases/reducers/148-session-agentchanged-clears-agent.json b/types/test-cases/reducers/148-session-agentchanged-clears-agent.json index 7b86ca22..9db0ab76 100644 --- a/types/test-cases/reducers/148-session-agentchanged-clears-agent.json +++ b/types/test-cases/reducers/148-session-agentchanged-clears-agent.json @@ -14,7 +14,7 @@ } }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -32,6 +32,6 @@ "agent": null }, "lifecycle": "creating", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/149-session-agentchanged-replaces-agent.json b/types/test-cases/reducers/149-session-agentchanged-replaces-agent.json index f8ec4acb..c3c8736e 100644 --- a/types/test-cases/reducers/149-session-agentchanged-replaces-agent.json +++ b/types/test-cases/reducers/149-session-agentchanged-replaces-agent.json @@ -14,7 +14,7 @@ } }, "lifecycle": "creating", - "chats": [] + "turns": [] }, "actions": [ { @@ -37,6 +37,6 @@ } }, "lifecycle": "creating", - "chats": [] + "turns": [] } } diff --git a/types/test-cases/reducers/152-session-customizationremoved-removes-container-and-children.json b/types/test-cases/reducers/152-session-customizationremoved-removes-container-and-children.json index c0676061..e96e2332 100644 --- a/types/test-cases/reducers/152-session-customizationremoved-removes-container-and-children.json +++ b/types/test-cases/reducers/152-session-customizationremoved-removes-container-and-children.json @@ -11,6 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -34,8 +35,7 @@ "name": "Plugin B", "enabled": true } - ], - "chats": [] + ] }, "actions": [ { @@ -53,6 +53,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -61,7 +62,6 @@ "name": "Plugin B", "enabled": true } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/153-session-customizationremoved-removes-child.json b/types/test-cases/reducers/153-session-customizationremoved-removes-child.json index c506cefc..380a27d8 100644 --- a/types/test-cases/reducers/153-session-customizationremoved-removes-child.json +++ b/types/test-cases/reducers/153-session-customizationremoved-removes-child.json @@ -11,6 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -33,8 +34,7 @@ } ] } - ], - "chats": [] + ] }, "actions": [ { @@ -52,6 +52,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -68,7 +69,6 @@ } ] } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/154-session-customizationremoved-noop-unknown-id.json b/types/test-cases/reducers/154-session-customizationremoved-noop-unknown-id.json index 777a5793..ee6d6b22 100644 --- a/types/test-cases/reducers/154-session-customizationremoved-noop-unknown-id.json +++ b/types/test-cases/reducers/154-session-customizationremoved-noop-unknown-id.json @@ -11,6 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -19,8 +20,7 @@ "name": "Plugin A", "enabled": true } - ], - "chats": [] + ] }, "actions": [ { @@ -38,6 +38,7 @@ "modifiedAt": 1000 }, "lifecycle": "creating", + "turns": [], "customizations": [ { "type": "plugin", @@ -46,7 +47,6 @@ "name": "Plugin A", "enabled": true } - ], - "chats": [] + ] } } diff --git a/types/test-cases/reducers/156-session-default-chat-changed.json b/types/test-cases/reducers/156-session-default-chat-changed.json deleted file mode 100644 index 564f55b6..00000000 --- a/types/test-cases/reducers/156-session-default-chat-changed.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "session/defaultChatChanged updates input-routing hint", - "reducer": "session", - "initial": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [], - "defaultChat": "ahp-chat:/old" - }, - "actions": [ - { - "type": "session/defaultChatChanged", - "defaultChat": "ahp-chat:/new" - } - ], - "expected": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [], - "defaultChat": "ahp-chat:/new" - } -} diff --git a/types/test-cases/reducers/158-toolcallconfirmed-approved-with-editedtoolinput-overrides-original.json b/types/test-cases/reducers/158-toolcallconfirmed-approved-with-editedtoolinput-overrides-original.json index 9afbae97..af97721f 100644 --- a/types/test-cases/reducers/158-toolcallconfirmed-approved-with-editedtoolinput-overrides-original.json +++ b/types/test-cases/reducers/158-toolcallconfirmed-approved-with-editedtoolinput-overrides-original.json @@ -1,7 +1,16 @@ { "description": "toolCallConfirmed approved with editedToolInput overrides the original toolInput in the running state", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,29 +22,25 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "bash", "displayName": "Run Command" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Run: ls -la", "toolInput": "ls -la" }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-1", "approved": true, @@ -43,7 +48,7 @@ "editedToolInput": "ls -la /tmp" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -53,6 +58,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -81,10 +95,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/159-session-mcpserverstatechanged-upserts-top-level-server.json b/types/test-cases/reducers/159-session-mcpserverstatechanged-upserts-top-level-server.json index 717f6a46..2ea7247a 100644 --- a/types/test-cases/reducers/159-session-mcpserverstatechanged-upserts-top-level-server.json +++ b/types/test-cases/reducers/159-session-mcpserverstatechanged-upserts-top-level-server.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [], + "turns": [], "customizations": [ { "type": "mcpServer", @@ -19,9 +19,7 @@ "uri": "file:///workspace/.mcp/servers.json", "name": "Filesystem", "enabled": true, - "state": { - "kind": "starting" - } + "state": { "kind": "starting" } } ] }, @@ -29,9 +27,7 @@ { "type": "session/mcpServerStateChanged", "id": "mcp-1", - "state": { - "kind": "ready" - }, + "state": { "kind": "ready" }, "channel": "mcp://filesystem" } ], @@ -45,7 +41,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [], + "turns": [], "customizations": [ { "type": "mcpServer", @@ -53,9 +49,7 @@ "uri": "file:///workspace/.mcp/servers.json", "name": "Filesystem", "enabled": true, - "state": { - "kind": "ready" - }, + "state": { "kind": "ready" }, "channel": "mcp://filesystem" } ] diff --git a/types/test-cases/reducers/160-session-default-chat-changed-unsets.json b/types/test-cases/reducers/160-session-default-chat-changed-unsets.json deleted file mode 100644 index 1766a487..00000000 --- a/types/test-cases/reducers/160-session-default-chat-changed-unsets.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "description": "session/defaultChatChanged unsets input-routing hint", - "reducer": "session", - "initial": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [], - "defaultChat": "ahp-chat:/old" - }, - "actions": [ - { - "type": "session/defaultChatChanged" - } - ], - "expected": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [], - "defaultChat": null - } -} diff --git a/types/test-cases/reducers/160-session-mcpserverstatechanged-upserts-container-child.json b/types/test-cases/reducers/160-session-mcpserverstatechanged-upserts-container-child.json index 5e674318..7218521a 100644 --- a/types/test-cases/reducers/160-session-mcpserverstatechanged-upserts-container-child.json +++ b/types/test-cases/reducers/160-session-mcpserverstatechanged-upserts-container-child.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [], + "turns": [], "customizations": [ { "type": "plugin", @@ -32,9 +32,7 @@ "uri": "https://plugins.example/a#mcp/search", "name": "Search", "enabled": true, - "state": { - "kind": "starting" - } + "state": { "kind": "starting" } } ] } @@ -44,9 +42,7 @@ { "type": "session/mcpServerStateChanged", "id": "mcp-child", - "state": { - "kind": "ready" - }, + "state": { "kind": "ready" }, "channel": "mcp://search" } ], @@ -60,7 +56,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [], + "turns": [], "customizations": [ { "type": "plugin", @@ -81,9 +77,7 @@ "uri": "https://plugins.example/a#mcp/search", "name": "Search", "enabled": true, - "state": { - "kind": "ready" - }, + "state": { "kind": "ready" }, "channel": "mcp://search" } ] diff --git a/types/test-cases/reducers/161-chat-turn-lifecycle-on-chat.json b/types/test-cases/reducers/161-chat-turn-lifecycle-on-chat.json deleted file mode 100644 index df65d14c..00000000 --- a/types/test-cases/reducers/161-chat-turn-lifecycle-on-chat.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "description": "chat turn lifecycle starts, streams, and completes on a chat channel", - "reducer": "chat", - "initial": { - "turns": [], - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { - "kind": "user" - } - }, - "actions": [ - { - "type": "chat/turnStarted", - "turnId": "turn-1", - "message": { - "text": "Hello", - "origin": { - "kind": "user" - } - } - }, - { - "type": "chat/responsePart", - "turnId": "turn-1", - "part": { - "kind": "markdown", - "id": "md-1", - "content": "" - } - }, - { - "type": "chat/delta", - "turnId": "turn-1", - "partId": "md-1", - "content": "Hello from chat" - }, - { - "type": "chat/turnComplete", - "turnId": "turn-1" - } - ], - "expected": { - "turns": [ - { - "id": "turn-1", - "message": { - "text": "Hello", - "origin": { - "kind": "user" - } - }, - "responseParts": [ - { - "kind": "markdown", - "id": "md-1", - "content": "Hello from chat" - } - ], - "usage": null, - "state": "complete", - "error": null - } - ], - "activeTurn": null, - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:09.999Z", - "origin": { - "kind": "user" - } - } -} diff --git a/types/test-cases/reducers/161-session-mcpserverstatechanged-noop-unknown-id.json b/types/test-cases/reducers/161-session-mcpserverstatechanged-noop-unknown-id.json index a4b5c948..e9712e57 100644 --- a/types/test-cases/reducers/161-session-mcpserverstatechanged-noop-unknown-id.json +++ b/types/test-cases/reducers/161-session-mcpserverstatechanged-noop-unknown-id.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [], + "turns": [], "customizations": [ { "type": "mcpServer", @@ -19,9 +19,7 @@ "uri": "file:///workspace/.mcp/servers.json", "name": "Filesystem", "enabled": true, - "state": { - "kind": "ready" - }, + "state": { "kind": "ready" }, "channel": "mcp://filesystem" } ] @@ -30,9 +28,7 @@ { "type": "session/mcpServerStateChanged", "id": "mcp-does-not-exist", - "state": { - "kind": "stopped" - } + "state": { "kind": "stopped" } } ], "expected": { @@ -45,7 +41,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [], + "turns": [], "customizations": [ { "type": "mcpServer", @@ -53,9 +49,7 @@ "uri": "file:///workspace/.mcp/servers.json", "name": "Filesystem", "enabled": true, - "state": { - "kind": "ready" - }, + "state": { "kind": "ready" }, "channel": "mcp://filesystem" } ] diff --git a/types/test-cases/reducers/162-session-mcpserverstatechanged-noop-non-mcp-id.json b/types/test-cases/reducers/162-session-mcpserverstatechanged-noop-non-mcp-id.json index 532658a0..0dd4e187 100644 --- a/types/test-cases/reducers/162-session-mcpserverstatechanged-noop-non-mcp-id.json +++ b/types/test-cases/reducers/162-session-mcpserverstatechanged-noop-non-mcp-id.json @@ -11,7 +11,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [], + "turns": [], "customizations": [ { "type": "plugin", @@ -34,17 +34,13 @@ { "type": "session/mcpServerStateChanged", "id": "plugin-a", - "state": { - "kind": "ready" - }, + "state": { "kind": "ready" }, "channel": "mcp://nope" }, { "type": "session/mcpServerStateChanged", "id": "skill-1", - "state": { - "kind": "ready" - }, + "state": { "kind": "ready" }, "channel": "mcp://nope" } ], @@ -58,7 +54,7 @@ "modifiedAt": 1000 }, "lifecycle": "ready", - "chats": [], + "turns": [], "customizations": [ { "type": "plugin", diff --git a/types/test-cases/reducers/163-toolcallstart-carries-mcp-contributor-through-lifecycle.json b/types/test-cases/reducers/163-toolcallstart-carries-mcp-contributor-through-lifecycle.json index cbe1b13d..3e8d40cb 100644 --- a/types/test-cases/reducers/163-toolcallstart-carries-mcp-contributor-through-lifecycle.json +++ b/types/test-cases/reducers/163-toolcallstart-carries-mcp-contributor-through-lifecycle.json @@ -1,7 +1,16 @@ { - "description": "chat/toolCallStart with a non-null mcp contributor carries the contributor through tcBase across ready and complete", - "reducer": "chat", + "description": "session/toolCallStart with a non-null mcp contributor carries the contributor through tcBase across ready and complete", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-1", "toolName": "search", @@ -32,7 +37,7 @@ } }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-1", "invocationMessage": "Search: foo", @@ -40,7 +45,7 @@ "confirmed": "not-needed" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-1", "result": { @@ -50,6 +55,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -81,10 +95,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } } diff --git a/types/test-cases/reducers/170-session-chatadded-appends.json b/types/test-cases/reducers/170-session-chatadded-appends.json deleted file mode 100644 index 781d920e..00000000 --- a/types/test-cases/reducers/170-session-chatadded-appends.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "description": "session/chatAdded appends a new chat to the catalog", - "reducer": "session", - "initial": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [] - }, - "actions": [ - { - "type": "session/chatAdded", - "summary": { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - } - } - ], - "expected": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - } - ] - } -} diff --git a/types/test-cases/reducers/171-session-chatadded-upserts.json b/types/test-cases/reducers/171-session-chatadded-upserts.json deleted file mode 100644 index efe8d3ed..00000000 --- a/types/test-cases/reducers/171-session-chatadded-upserts.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "description": "session/chatAdded upserts an existing chat by resource", - "reducer": "session", - "initial": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - } - ] - }, - "actions": [ - { - "type": "session/chatAdded", - "summary": { - "resource": "ahp-chat:/c1", - "title": "Chat 1 (renamed)", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z", - "origin": { "kind": "user" } - } - } - ], - "expected": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1 (renamed)", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z", - "origin": { "kind": "user" } - } - ] - } -} diff --git a/types/test-cases/reducers/172-session-chatremoved.json b/types/test-cases/reducers/172-session-chatremoved.json deleted file mode 100644 index 7a9cfed6..00000000 --- a/types/test-cases/reducers/172-session-chatremoved.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "description": "session/chatRemoved removes a chat and clears defaultChat when it matches", - "reducer": "session", - "initial": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - }, - { - "resource": "ahp-chat:/c2", - "title": "Chat 2", - "status": 8, - "activity": "Thinking", - "modifiedAt": "1970-01-01T00:00:02.000Z", - "origin": { "kind": "fork", "chat": "ahp-chat:/c1", "turnId": "t1" } - } - ], - "defaultChat": "ahp-chat:/c1" - }, - "actions": [ - { - "type": "session/chatRemoved", - "chat": "ahp-chat:/c1" - } - ], - "expected": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c2", - "title": "Chat 2", - "status": 8, - "activity": "Thinking", - "modifiedAt": "1970-01-01T00:00:02.000Z", - "origin": { "kind": "fork", "chat": "ahp-chat:/c1", "turnId": "t1" } - } - ] - } -} diff --git a/types/test-cases/reducers/173-session-chatupdated.json b/types/test-cases/reducers/173-session-chatupdated.json deleted file mode 100644 index 0e134492..00000000 --- a/types/test-cases/reducers/173-session-chatupdated.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "description": "session/chatUpdated merges partial changes into an existing chat", - "reducer": "session", - "initial": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - } - ] - }, - "actions": [ - { - "type": "session/chatUpdated", - "chat": "ahp-chat:/c1", - "changes": { - "status": 24, - "activity": "Waiting for approval", - "modifiedAt": "1970-01-01T00:00:02.000Z" - } - } - ], - "expected": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 24, - "activity": "Waiting for approval", - "modifiedAt": "1970-01-01T00:00:02.000Z", - "origin": { "kind": "user" } - } - ] - } -} diff --git a/types/test-cases/reducers/174-session-chatremoved-noop.json b/types/test-cases/reducers/174-session-chatremoved-noop.json deleted file mode 100644 index 87c81019..00000000 --- a/types/test-cases/reducers/174-session-chatremoved-noop.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "description": "session/chatRemoved is a no-op when the chat URI is unknown", - "reducer": "session", - "initial": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - } - ], - "defaultChat": "ahp-chat:/c1" - }, - "actions": [ - { - "type": "session/chatRemoved", - "chat": "ahp-chat:/cX" - } - ], - "expected": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - } - ], - "defaultChat": "ahp-chat:/c1" - } -} diff --git a/types/test-cases/reducers/175-session-chatupdated-noop.json b/types/test-cases/reducers/175-session-chatupdated-noop.json deleted file mode 100644 index d262b6c7..00000000 --- a/types/test-cases/reducers/175-session-chatupdated-noop.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "description": "session/chatUpdated is a no-op when the chat URI is unknown", - "reducer": "session", - "initial": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - } - ] - }, - "actions": [ - { - "type": "session/chatUpdated", - "chat": "ahp-chat:/cX", - "changes": { "title": "Never written" } - } - ], - "expected": { - "summary": { - "resource": "ahp-session:/s1", - "provider": "copilot", - "title": "Session", - "status": 1, - "createdAt": 1000, - "modifiedAt": 1000 - }, - "lifecycle": "ready", - "chats": [ - { - "resource": "ahp-chat:/c1", - "title": "Chat 1", - "status": 1, - "modifiedAt": "1970-01-01T00:00:01.000Z", - "origin": { "kind": "user" } - } - ] - } -} diff --git a/types/test-cases/reducers/220-toolcall-actions-update-meta.json b/types/test-cases/reducers/220-toolcall-actions-update-meta.json index fbabd4a0..e173a13e 100644 --- a/types/test-cases/reducers/220-toolcall-actions-update-meta.json +++ b/types/test-cases/reducers/220-toolcall-actions-update-meta.json @@ -1,7 +1,16 @@ { "description": "tool-call-scoped actions update tool call _meta", - "reducer": "chat", + "reducer": "session", "initial": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 8, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -13,15 +22,11 @@ }, "responseParts": [], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 8, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } }, "actions": [ { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-delta", "toolName": "stream", @@ -31,7 +36,7 @@ } }, { - "type": "chat/toolCallDelta", + "type": "session/toolCallDelta", "turnId": "turn-1", "toolCallId": "tc-delta", "content": "abc", @@ -41,14 +46,14 @@ } }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-ready-running", "toolName": "run", "displayName": "Run Tool" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-ready-running", "invocationMessage": "Run now", @@ -59,14 +64,14 @@ } }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-ready-pending", "toolName": "ask", "displayName": "Ask Tool" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-ready-pending", "invocationMessage": "Needs approval", @@ -75,20 +80,20 @@ } }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-confirm-approved", "toolName": "approve", "displayName": "Approve Tool" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-confirm-approved", "invocationMessage": "Approve this" }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-confirm-approved", "approved": true, @@ -98,20 +103,20 @@ } }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-confirm-denied", "toolName": "deny", "displayName": "Deny Tool" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-confirm-denied", "invocationMessage": "Deny this" }, { - "type": "chat/toolCallConfirmed", + "type": "session/toolCallConfirmed", "turnId": "turn-1", "toolCallId": "tc-confirm-denied", "approved": false, @@ -121,21 +126,21 @@ } }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-complete", "toolName": "complete", "displayName": "Complete Tool" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-complete", "invocationMessage": "Complete this", "confirmed": "not-needed" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-complete", "result": { @@ -147,21 +152,21 @@ } }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-result-confirmed", "toolName": "result", "displayName": "Result Tool" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-result-confirmed", "invocationMessage": "Result this", "confirmed": "not-needed" }, { - "type": "chat/toolCallComplete", + "type": "session/toolCallComplete", "turnId": "turn-1", "toolCallId": "tc-result-confirmed", "result": { @@ -174,7 +179,7 @@ } }, { - "type": "chat/toolCallResultConfirmed", + "type": "session/toolCallResultConfirmed", "turnId": "turn-1", "toolCallId": "tc-result-confirmed", "approved": true, @@ -183,21 +188,21 @@ } }, { - "type": "chat/toolCallStart", + "type": "session/toolCallStart", "turnId": "turn-1", "toolCallId": "tc-content", "toolName": "content", "displayName": "Content Tool" }, { - "type": "chat/toolCallReady", + "type": "session/toolCallReady", "turnId": "turn-1", "toolCallId": "tc-content", "invocationMessage": "Content this", "confirmed": "not-needed" }, { - "type": "chat/toolCallContentChanged", + "type": "session/toolCallContentChanged", "turnId": "turn-1", "toolCallId": "tc-content", "content": [ @@ -213,6 +218,15 @@ } ], "expected": { + "summary": { + "resource": "copilot:/test-session", + "provider": "copilot", + "title": "Test Session", + "status": 24, + "createdAt": 1000, + "modifiedAt": 2000 + }, + "lifecycle": "ready", "turns": [], "activeTurn": { "id": "turn-1", @@ -226,6 +240,7 @@ { "kind": "toolCall", "toolCall": { + "status": "streaming", "toolCallId": "tc-delta", "toolName": "stream", "displayName": "Stream Tool", @@ -233,7 +248,6 @@ "_meta": { "phase": "delta" }, - "status": "streaming", "partialInput": "abc", "invocationMessage": "Streaming input" } @@ -370,10 +384,6 @@ } ], "usage": null - }, - "resource": "copilot:/test-session", - "title": "Test Session", - "status": 24, - "modifiedAt": "1970-01-01T00:00:02.000Z" + } } -} +} \ No newline at end of file diff --git a/types/version/message-checks.ts b/types/version/message-checks.ts index 414602d6..a97ee2f1 100644 --- a/types/version/message-checks.ts +++ b/types/version/message-checks.ts @@ -56,8 +56,6 @@ type _ExpectedCommands = | 'subscribe' | 'createSession' | 'disposeSession' - | 'createChat' - | 'disposeChat' | 'createTerminal' | 'disposeTerminal' | 'createResourceWatch' diff --git a/types/version/registry.ts b/types/version/registry.ts index 8a0ce6e3..61de3784 100644 --- a/types/version/registry.ts +++ b/types/version/registry.ts @@ -80,49 +80,45 @@ export const ACTION_INTRODUCED_IN: { readonly [K in StateAction['type']]: string [ActionType.RootActiveSessionsChanged]: '0.1.0', [ActionType.SessionReady]: '0.1.0', [ActionType.SessionCreationFailed]: '0.1.0', - [ActionType.SessionChatAdded]: '0.4.0', - [ActionType.SessionChatRemoved]: '0.4.0', - [ActionType.SessionChatUpdated]: '0.4.0', - [ActionType.SessionDefaultChatChanged]: '0.4.0', + [ActionType.SessionTurnStarted]: '0.1.0', + [ActionType.SessionDelta]: '0.1.0', + [ActionType.SessionResponsePart]: '0.1.0', + [ActionType.SessionToolCallStart]: '0.1.0', + [ActionType.SessionToolCallDelta]: '0.1.0', + [ActionType.SessionToolCallReady]: '0.1.0', + [ActionType.SessionToolCallConfirmed]: '0.1.0', + [ActionType.SessionToolCallComplete]: '0.1.0', + [ActionType.SessionToolCallResultConfirmed]: '0.1.0', + [ActionType.SessionToolCallContentChanged]: '0.1.0', + [ActionType.SessionTurnComplete]: '0.1.0', + [ActionType.SessionTurnCancelled]: '0.1.0', + [ActionType.SessionError]: '0.1.0', [ActionType.SessionTitleChanged]: '0.1.0', + [ActionType.SessionUsage]: '0.1.0', + [ActionType.SessionReasoning]: '0.1.0', [ActionType.SessionModelChanged]: '0.1.0', [ActionType.SessionAgentChanged]: '0.2.0', [ActionType.SessionServerToolsChanged]: '0.1.0', [ActionType.SessionActiveClientChanged]: '0.1.0', [ActionType.SessionActiveClientToolsChanged]: '0.1.0', + [ActionType.SessionPendingMessageSet]: '0.1.0', + [ActionType.SessionPendingMessageRemoved]: '0.1.0', + [ActionType.SessionQueuedMessagesReordered]: '0.1.0', + [ActionType.SessionInputRequested]: '0.1.0', + [ActionType.SessionInputAnswerChanged]: '0.1.0', + [ActionType.SessionInputCompleted]: '0.1.0', [ActionType.SessionCustomizationsChanged]: '0.1.0', [ActionType.SessionCustomizationToggled]: '0.1.0', [ActionType.SessionCustomizationUpdated]: '0.1.0', [ActionType.SessionCustomizationRemoved]: '0.2.0', [ActionType.SessionMcpServerStateChanged]: '0.3.0', + [ActionType.SessionTruncated]: '0.1.0', [ActionType.SessionIsReadChanged]: '0.1.0', [ActionType.SessionIsArchivedChanged]: '0.1.0', [ActionType.SessionActivityChanged]: '0.1.0', [ActionType.SessionChangesetsChanged]: '0.2.0', [ActionType.SessionConfigChanged]: '0.1.0', [ActionType.SessionMetaChanged]: '0.1.0', - [ActionType.ChatTurnStarted]: '0.4.0', - [ActionType.ChatDelta]: '0.4.0', - [ActionType.ChatResponsePart]: '0.4.0', - [ActionType.ChatToolCallStart]: '0.4.0', - [ActionType.ChatToolCallDelta]: '0.4.0', - [ActionType.ChatToolCallReady]: '0.4.0', - [ActionType.ChatToolCallConfirmed]: '0.4.0', - [ActionType.ChatToolCallComplete]: '0.4.0', - [ActionType.ChatToolCallResultConfirmed]: '0.4.0', - [ActionType.ChatToolCallContentChanged]: '0.4.0', - [ActionType.ChatTurnComplete]: '0.4.0', - [ActionType.ChatTurnCancelled]: '0.4.0', - [ActionType.ChatError]: '0.4.0', - [ActionType.ChatUsage]: '0.4.0', - [ActionType.ChatReasoning]: '0.4.0', - [ActionType.ChatPendingMessageSet]: '0.4.0', - [ActionType.ChatPendingMessageRemoved]: '0.4.0', - [ActionType.ChatQueuedMessagesReordered]: '0.4.0', - [ActionType.ChatInputRequested]: '0.4.0', - [ActionType.ChatInputAnswerChanged]: '0.4.0', - [ActionType.ChatInputCompleted]: '0.4.0', - [ActionType.ChatTruncated]: '0.4.0', [ActionType.ChangesetStatusChanged]: '0.2.0', [ActionType.ChangesetFileSet]: '0.2.0', [ActionType.ChangesetFileRemoved]: '0.2.0',