// Generated from types/*.ts — do not edit. // Regenerate with: npm run generate:typescript /** * Session State Types — Per-session coordination state exposed on `ahp-session:` channels. * * Stability: 2 - Stable * * @module channels-session/state */ import type { Changeset } from '../channels-changeset/state.js'; import type { AnnotationsSummary } from '../channels-annotations/state.js'; import type { ChatSummary, ChatInputRequest, ToolCallConfirmationState, ToolCallRunningState, ToolCallAuthRequiredState, } from '../channels-chat/state.js'; import type { AutomationRunState } from '../channels-automation-run/state.js'; import type { AutomationEntry } from '../channels-automation/state.js'; import type { ConfigPropertySchema, ErrorInfo, Icon, ProtectedResourceMetadata, TextRange, URI, } from '../common/state.js'; // ─── Session State ─────────────────────────────────────────────────────────── /** * Session initialization state. * * @category Session State * @nonexhaustive */ export const enum SessionLifecycle { Creating = 'creating', Ready = 'ready', Failed = 'failed', } /** * Bitset of summary-level session status flags. * * Use bitwise checks instead of equality for non-terminal activity. For example, * `status & SessionStatus.InProgress` matches both ordinary in-progress turns * and turns that are paused waiting for input. * * @category Session State * @nonexhaustive */ export const enum SessionStatus { /** Session is idle — no turn is active. */ Idle = 1, /** Session ended with an error. */ Error = 1 << 1, /** A turn is actively streaming. */ InProgress = 1 << 3, /** A turn is in progress but blocked waiting for user input or tool confirmation. */ InputNeeded = (1 << 3) | (1 << 4), /** The client has viewed this session since its last modification. */ IsRead = 1 << 5, /** The session has been archived by the client. */ IsArchived = 1 << 6, } /** * Discriminant describing the durable provenance of a session. * * @category Session State * @nonexhaustive */ export const enum SessionOriginKind { /** The session was created as part of an automation run. */ Automation = 'automation', } /** * Provenance recorded on a session created for an automation run. * * The links let clients navigate from an ordinary session to the task-level * run and its durable definition. The session channel remains authoritative * for this session's transcript, tools, confirmations, and changes. * * @category Session State */ export interface AutomationSessionOrigin { kind: SessionOriginKind.Automation; /** Owning {@link AutomationEntry.resource}. */ automation: URI; /** Owning {@link AutomationRunState.resource}. */ run: URI; } /** * Durable provenance for sessions created by a higher-level AHP workflow. * * @category Session State */ export type SessionOrigin = AutomationSessionOrigin; /** * Metadata shared between the full {@link SessionState} (delivered when a * client subscribes to a session's URI) and the lightweight * {@link SessionSummary} (carried in the root-channel session catalog). * * These fields describe the session at a glance and appear in both places. * `SessionState` owns the authoritative values for a subscribed session; * `SessionSummary` mirrors them into the catalog so clients that only render a * session list don't have to subscribe to every session URI. The host keeps * the catalog in sync via `root/sessionSummaryChanged`. * * @category Session State */ export interface SessionMetadata { /** Agent provider ID */ provider: string; /** Session title */ title: string; /** Current session status */ status: SessionStatus; /** Human-readable description of what the session is currently doing */ activity?: string; /** Durable {@link AutomationSessionOrigin}, when an automation run created this session. */ origin?: SessionOrigin; /** Server-owned project for this session */ project?: ProjectInfo; /** * The working directories the session's agent has tool access to, as * maintained by working-directory actions. Directories are equal peers except * when the agent advertises * {@link MultipleWorkingDirectoriesCapability.immutablePrimary} without * {@link MultipleWorkingDirectoriesCapability.primaryReplacement} (the first * entry is then a fixed process root), or advertises `primaryReplacement` * (the first entry is a protected, replaceable primary slot). Individual chats * MAY restrict to a subset via * {@link ChatSummary.workingDirectories | their own `workingDirectories`}; a * chat that sets none operates against this full set. */ workingDirectories?: URI[]; /** * Lightweight summary of this session's inline annotations channel * (`ahp-session://annotations`). Surfaced so badge UI can render * annotation / entry counts without subscribing. Absent when the session * does not expose an annotations channel. */ annotations?: AnnotationsSummary; } /** * Full state for a single session, loaded when a client subscribes to the session's URI. * * Inlines (denormalizes) every {@link SessionMetadata} field directly onto * itself so subscribers receive one flat object instead of a nested summary. * The lightweight catalog representation is {@link SessionSummary}, surfaced on * the root channel; the host keeps the two in sync via * `root/sessionSummaryChanged`. * * @category Session State */ export interface SessionState extends SessionMetadata { /** Session initialization state */ lifecycle: SessionLifecycle; /** Error details if creation failed */ creationError?: ErrorInfo; /** Tools provided by the server (agent host) for this session */ serverTools?: ToolDefinition[]; /** * The clients currently providing tools and interactive capabilities to this * session. If multiple tools or customizations are provided by the same * active client, an agent host MAY deduplicate them when exposed to a model, * with a preference given to the client that started the turn. * * Membership is host-managed: clients add (or refresh) themselves with * `session/activeClientSet`, and the host removes them with * `session/activeClientRemoved` when they unsubscribe, disconnect without * reconnecting in time, or reconnect without resubscribing to the session. */ activeClients: 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; /** Session configuration schema and current values */ config?: SessionConfigState; /** * Top-level customizations active in this session. * * Always one of the {@link Customization} variants: * * - Container customizations ({@link PluginCustomization}, * {@link DirectoryCustomization}) whose children — agents, skills, * prompts, rules, hooks, MCP servers — live in each container's * {@link ContainerCustomizationBase.children | `children`} array. * - Top-level {@link McpServerCustomization} entries the host * surfaces directly (for example a globally-configured MCP server * that isn't bundled in a plugin or directory). MCP servers may * also appear as children of a container. * * Client-published plugins arrive via * {@link SessionActiveClient.customizations | `activeClients[].customizations`} * and the host propagates them into this list (typically with the * container's `clientId` set and `children` populated). Clients * publish in container shape only; bare MCP servers at the top level * are server-originated. */ customizations?: Customization[]; /** * Catalogue of changesets the server can produce for this session. Each * entry advertises a subscribable view of file changes (uncommitted, * session-wide, per-turn, etc.) and the URI template the client expands * before subscribing. See {@link Changeset} for the full shape and * {@link /guide/changesets | Changesets} for an overview of the model. */ changesets?: Changeset[]; /** * Outstanding input the session is blocked on, aggregated across every chat * so a client can discover and answer it from the session channel alone, * without subscribing to individual chats. * * Each entry is self-sufficient: it carries the owning chat's URI plus every * identifier the client needs to respond. A client answers by dispatching the * ordinary `chat/*` action to that chat's channel — see * {@link SessionInputRequest} for the per-variant response path. A list * holding any entry other than * {@link SessionInputRequestKind.ToolClientExecution} implies * {@link SessionStatus.InputNeeded} on {@link SessionSummary.status}; * client-execution entries are work delegated to a client rather than a * prompt, so they leave the session's activity unchanged. * * Host-managed: the host upserts entries with `session/inputNeededSet` as * chats raise requests and removes them with `session/inputNeededRemoved` * once the underlying request resolves. */ inputNeeded?: SessionInputRequest[]; /** * Additional provider-specific metadata for this session. * * Clients MAY look for well-known keys here to provide enhanced UI. * For example, a `git` key may provide extra git metadata about the session's * working directories. */ _meta?: Record; } /** * A client currently providing tools and interactive capabilities to a session. * * A session MAY have several active clients at once; entries in * {@link SessionState.activeClients} are keyed by `clientId`. The server SHOULD * automatically remove an active client when that client disconnects. * * @category Session State */ export interface SessionActiveClient { /** Client identifier (matches `clientId` from `initialize`) */ clientId: string; /** Human-readable client name (e.g. `"VS Code"`) */ displayName?: string; /** Tools this client provides to the session */ tools: ToolDefinition[]; /** * Plugin customizations this client contributes to the session. * * Clients publish in [Open Plugins](https://open-plugins.com/) format * — i.e. always container-shaped plugins. They MAY synthesize virtual * plugins in memory and rely on the host to expand them into concrete * children inside {@link SessionState.customizations}. */ customizations?: ClientPluginCustomization[]; } // ─── Session Input Requests ────────────────────────────────────────────────── /** * Discriminant for the kinds of outstanding input a session can surface in * {@link SessionState.inputNeeded}. * * This is a general/typological union (not a lifecycle), so the discriminant is * a `*Kind`. * * @category Session Input Types * @nonexhaustive */ export const enum SessionInputRequestKind { /** A user-facing elicitation mirrored from an unresolved chat response part. */ ChatInput = 'chatInput', /** A tool call awaiting parameter- or result-confirmation. */ ToolConfirmation = 'toolConfirmation', /** A running tool the session wants an active client to execute. */ ToolClientExecution = 'toolClientExecution', /** A tool call blocked on MCP authentication mid-execution. */ ToolAuthentication = 'toolAuthentication', } /** * Fields common to every {@link SessionInputRequest} variant. * * @category Session Input Types */ interface SessionInputRequestBase { /** * Stable key for this entry, unique within the session's * {@link SessionState.inputNeeded} list. The host derives it however it likes * (for example from the chat URI plus the underlying request or tool-call * id); consumers MUST treat it as opaque. It is the key for the * `session/inputNeededSet` / `session/inputNeededRemoved` upsert convention. */ id: string; /** * The chat the underlying request lives in. This is the channel a client * dispatches its response to — it does not need to have subscribed to that * chat first. */ chat: URI; } /** * A user-input elicitation surfaced at the session level, mirroring the request * from an unresolved {@link InputRequestResponsePart} in the owning chat. * * Respond by dispatching `chat/inputCompleted` (or syncing drafts with * `chat/inputAnswerChanged`) to {@link SessionInputRequestBase.chat | `chat`}, * keyed by {@link ChatInputRequest.id | `request.id`}. * * @category Session Input Types */ export interface SessionChatInputRequest extends SessionInputRequestBase { kind: SessionInputRequestKind.ChatInput; /** The mirrored chat input request. */ request: ChatInputRequest; } /** * A tool call blocked on confirmation — either parameter confirmation before * execution or result confirmation after — surfaced at the session level. * * Respond by dispatching `chat/toolCallConfirmed` (for * {@link ToolCallPendingConfirmationState}) or `chat/toolCallResultConfirmed` * (for {@link ToolCallPendingResultConfirmationState}) to * {@link SessionInputRequestBase.chat | `chat`}, keyed by `turnId` and * `toolCall.toolCallId`. * * @category Session Input Types */ export interface SessionToolConfirmationRequest extends SessionInputRequestBase { kind: SessionInputRequestKind.ToolConfirmation; /** The turn the tool call belongs to. */ turnId: string; /** The tool call awaiting confirmation. */ toolCall: ToolCallConfirmationState; } /** * A running tool whose execution is delegated to an active client. Surfaced so * a client that provides the tool can pick up the work without subscribing to * the owning chat. * * The {@link toolCall} is always a {@link ToolCallRunningState} (a * {@link ToolCallState} in `running` status) whose * {@link ToolCallRunningState.contributor | `contributor`} is a client * {@link ToolCallClientContributor} whose `clientId` matches the denormalized * {@link clientId} here. Execute and report the result by dispatching * `chat/toolCallComplete` (and optionally streaming with * `chat/toolCallContentChanged`) to {@link SessionInputRequestBase.chat | * `chat`}, keyed by `turnId` and `toolCall.toolCallId`. * * Unlike the other variants this does **not** raise * {@link SessionStatus.InputNeeded}: the call has already cleared its * confirmation gate and is merely executing elsewhere, so the session stays * {@link SessionStatus.InProgress} while it runs. * * @category Session Input Types */ export interface SessionToolClientExecutionRequest extends SessionInputRequestBase { kind: SessionInputRequestKind.ToolClientExecution; /** The turn the tool call belongs to. */ turnId: string; /** * The `clientId` expected to execute the tool. Matches the `clientId` of the * tool call's client {@link ToolCallContributor}. */ clientId: string; /** * The running tool call the session wants the owning client to execute. The * host only ever populates this with a {@link ToolCallRunningState}. */ toolCall: ToolCallRunningState; } /** * A tool call blocked on MCP authentication mid-execution, surfaced at the * session level. * * The {@link toolCall} is always a {@link ToolCallAuthRequiredState} (a * {@link ToolCallState} in `auth-required` status). Unlike * {@link SessionToolConfirmationRequest}, this is **not** answered by * dispatching a `chat/*` action directly: the client obtains a token for * {@link ToolCallAuthRequiredState.auth | `toolCall.auth`}`.resource` and * pushes it via the existing `authenticate` command (see * {@link /specification/authentication | Authentication}). The host resumes * the tool call and dispatches `chat/toolCallAuthResolved` once the token is * accepted, at which point it also removes this entry with * `session/inputNeededRemoved`. * * @category Session Input Types */ export interface SessionToolAuthenticationRequest extends SessionInputRequestBase { kind: SessionInputRequestKind.ToolAuthentication; /** The turn the tool call belongs to. */ turnId: string; /** The tool call awaiting authentication. */ toolCall: ToolCallAuthRequiredState; } /** * One outstanding piece of input a session is blocked on, aggregated across all * chats in {@link SessionState.inputNeeded}. * * Each entry is self-sufficient: it carries the owning * {@link SessionInputRequestBase.chat | `chat`} URI plus every identifier needed * to construct the response, so a client can answer by dispatching the ordinary * `chat/*` action (`chat/inputCompleted`, `chat/toolCallConfirmed`, * `chat/toolCallComplete`, …) to that chat's channel **without having subscribed * to the chat** — except {@link SessionToolAuthenticationRequest}, which is * resolved via the `authenticate` command instead. The host removes the entry * with `session/inputNeededRemoved` once the underlying request resolves. * * @category Session Input Types */ export type SessionInputRequest = | SessionChatInputRequest | SessionToolConfirmationRequest | SessionToolClientExecutionRequest | SessionToolAuthenticationRequest; /** * Server-owned project metadata for a session. * * @category Session State */ export interface ProjectInfo { /** Project URI */ uri: URI; /** Human-readable project name */ displayName: string; } /** * 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`. * - `workingDirectories`: the session-level set. Individual chats MAY restrict * to a subset via {@link ChatSummary.workingDirectories}; 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 extends SessionMetadata { /** Session URI */ resource: URI; /** Creation timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) */ createdAt: string; /** Last modification timestamp (ISO 8601, e.g. `"2025-03-10T18:42:03.123Z"`) */ modifiedAt: 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 * session's footprint (e.g., for list rendering) without requiring the * client to subscribe to a changeset. */ changes?: ChangesSummary; /** * Lightweight server-defined metadata clients may use for the session * presentation. The protocol does not interpret these values; producers * SHOULD keep the payload small because summaries appear in session lists * and session notifications. */ _meta?: Record; } /** * Aggregate counts describing the file changes associated with a session. * * All fields are optional so servers can populate only the metrics they * cheaply have available. * * @category Session State */ export interface ChangesSummary { /** Total number of inserted lines across all changed files. */ additions?: number; /** Total number of deleted lines across all changed files. */ deletions?: number; /** Number of files that have changes. */ files?: number; } // ─── Agent Selection ───────────────────────────────────────────────────────── /** * A selected custom agent for a session. * * The `uri` identifies a specific custom agent (matching an * {@link AgentCustomization.uri | `AgentCustomization.uri`} exposed via * the session's effective customizations). Consumers resolve the agent's * display name by looking up `uri` in the session's customization tree. * * A message with no `agent` selected uses the provider's default behavior. * * @category Session State */ export interface AgentSelection { /** Stable agent URI (matches an {@link AgentCustomization.uri}). */ uri: URI; } // ─── Session Config Types ──────────────────────────────────────────────────── /** * A session configuration property descriptor. * * Extends the generic {@link ConfigPropertySchema} with session-specific * display extensions. * * @category Session Config Types */ export interface SessionConfigPropertySchema extends ConfigPropertySchema { /** * Display extension: when `true`, the full set of allowed values is too large * to enumerate statically. The client SHOULD use `sessionConfigCompletions` * to fetch matching values based on user input. Any values in `enum` are * seed/recent values for initial display. */ enumDynamic?: boolean; /** When `true`, the user may change this property after session creation */ sessionMutable?: boolean; } /** * A JSON Schema object describing available session configuration metadata. * * @category Session Config Types */ export interface SessionConfigSchema { /** JSON Schema: always `'object'` */ type: 'object'; /** JSON Schema: property descriptors keyed by property id */ properties: Record; /** JSON Schema: list of required property ids */ required?: string[]; } /** * Live session configuration metadata. * * The schema describes the available configuration properties and the values * contain the current value for each resolved property. * * @category Session Config Types */ export interface SessionConfigState { /** JSON Schema describing available configuration properties */ schema: SessionConfigSchema; /** Current configuration values */ values: Record; } // ─── Tool Definition Types ─────────────────────────────────────────────────── /** * Describes a tool available in a session, provided by either the server or the active client. * * @category Tool Definition Types */ export interface ToolDefinition { /** Unique tool identifier */ name: string; /** Human-readable display name */ title?: string; /** Description of what the tool does */ description?: string; /** * JSON Schema defining the expected input parameters. * * Optional because client-provided tools may not have formal schemas. * Mirrors MCP `Tool.inputSchema`. */ inputSchema?: { type: 'object'; properties?: Record; required?: string[]; }; /** * JSON Schema defining the structure of the tool's output. * * Mirrors MCP `Tool.outputSchema`. */ outputSchema?: { type: 'object'; properties?: Record; required?: string[]; }; /** Behavioral hints about the tool. All properties are advisory. */ annotations?: ToolAnnotations; /** * Additional provider-specific metadata. * * Mirrors the MCP `_meta` convention. */ _meta?: Record; } /** * Behavioral hints about a tool. All properties are advisory and not * guaranteed to faithfully describe tool behavior. * * Mirrors MCP `ToolAnnotations` from the Model Context Protocol specification. * * @category Tool Definition Types */ export interface ToolAnnotations { /** Alternate human-readable title */ title?: string; /** Tool does not modify its environment (default: false) */ readOnlyHint?: boolean; /** Tool may perform destructive updates (default: true) */ destructiveHint?: boolean; /** Repeated calls with the same arguments have no additional effect (default: false) */ idempotentHint?: boolean; /** Tool may interact with external entities (default: true) */ openWorldHint?: boolean; } // ─── Customization Types ───────────────────────────────────────────────────── /** * Discriminant for the kind of customization. * * Top-level entries in {@link SessionState.customizations} and * {@link AgentInfo.customizations} are either container customizations * ({@link CustomizationType.Plugin | `Plugin`} or * {@link CustomizationType.Directory | `Directory`}) or * {@link CustomizationType.McpServer | `McpServer`} entries surfaced * directly by the host. The remaining types appear only as children of * a container. * * @category Customization Types * @nonexhaustive */ export const enum CustomizationType { Plugin = 'plugin', Directory = 'directory', Agent = 'agent', Skill = 'skill', Prompt = 'prompt', Rule = 'rule', Hook = 'hook', McpServer = 'mcpServer', } /** * Scope at which customization enablement is decided. * * @category Customization Types * @nonexhaustive */ export const enum CustomizationEnablementKind { Global = 'global', Workspace = 'workspace', Session = 'session', } /** A single explicit enablement decision. */ export type CustomizationEnablement = | { kind: CustomizationEnablementKind.Global; enabled: boolean } | { kind: CustomizationEnablementKind.Workspace; uri: URI; enabled: boolean } | { kind: CustomizationEnablementKind.Session; enabled: boolean }; /** * Customization types that appear as children of a * {@link PluginCustomization} or {@link DirectoryCustomization}. * * @category Customization Types */ export type ChildCustomizationType = | CustomizationType.Agent | CustomizationType.Skill | CustomizationType.Prompt | CustomizationType.Rule | CustomizationType.Hook | CustomizationType.McpServer; /** * Fields shared by every customization variant. * * @category Customization Types */ interface CustomizationBase { /** * Session-unique opaque identifier. Used by every action that targets a * specific customization. Minted by whoever publishes the customization * (typically the agent host). */ id: string; /** * Source URI for this customization. A plugin URL, a file URI, or a * directory URI. * * For declarations that live inside a larger file — e.g. an MCP * server declared inline in a `plugins.json` manifest — `uri` points * to the containing file and {@link CustomizationBase.range | `range`} * narrows it to the declaration's span. */ uri: URI; /** Human-readable name. */ name: string; /** Icons for UI display. */ icons?: Icon[]; /** * Optional span within {@link CustomizationBase.uri | `uri`} when this * customization is a subset of a larger file (for example, one entry * in an inline `mcpServers` block of a `plugins.json` manifest). * Absent when the customization covers the whole resource. */ range?: TextRange; /** * Additional provider-specific metadata for this customization. * * Mirrors the MCP `_meta` convention. Optional and opaque to the * protocol; producers and consumers agree on its contents * out-of-band. */ _meta?: Record; } /** * Discriminant values for {@link CustomizationLoadState}. * * @category Customization Types * @exhaustive */ export const enum CustomizationLoadStatus { Loading = 'loading', Loaded = 'loaded', Degraded = 'degraded', Error = 'error', } /** * Container is being loaded by the host. * * @category Customization Types */ export interface CustomizationLoadingState { kind: CustomizationLoadStatus.Loading; } /** * Container loaded successfully. * * @category Customization Types */ export interface CustomizationLoadedState { kind: CustomizationLoadStatus.Loaded; } /** * Container partially loaded but has warnings. * * @category Customization Types */ export interface CustomizationDegradedState { kind: CustomizationLoadStatus.Degraded; /** Human-readable description of the warning. */ message: string; } /** * Container failed to load. * * @category Customization Types */ export interface CustomizationErrorState { kind: CustomizationLoadStatus.Error; /** Human-readable error message. */ message: string; } /** * Discriminated load state for a container customization * ({@link PluginCustomization} or {@link DirectoryCustomization}). * * @category Customization Types */ export type CustomizationLoadState = | CustomizationLoadingState | CustomizationLoadedState | CustomizationDegradedState | CustomizationErrorState; /** * Fields shared by container customizations. * * @category Customization Types */ interface ContainerCustomizationBase extends CustomizationBase { /** * `clientId` of the client that contributed this container. Absent for * server-originated entries. */ clientId?: string; /** * Host-reported load state. Absent means the host has not yet reported * a load state for this container. */ load?: CustomizationLoadState; /** * Children discovered inside this container. * * Absent means the host has not parsed this container yet. An empty * array means the host parsed the container and it contributes * nothing. */ children?: ChildCustomization[]; } /** * An [Open Plugins](https://open-plugins.com/) plugin. * * @category Customization Types */ export interface PluginCustomization extends ContainerCustomizationBase { type: CustomizationType.Plugin; /** Explicit enablement decisions. See {@link McpServerCustomization.enablement}. */ enablement?: CustomizationEnablement[]; /** * Version of the plugin, sourced from the * [Open Plugins](https://open-plugins.com/) manifest's optional * `version` field (semver, e.g. `"1.2.0"`). Absent when the manifest * declares no version — the field is optional there — or the source * has no version concept. Provenance / display only: the host neither * parses nor enforces it. */ version?: string; } /** * A {@link PluginCustomization} as published by a client. Extends the * server-facing shape with an opaque `nonce` so the host can detect when * the client's view of a plugin has changed and re-parse only as needed. * * Clients SHOULD include a `nonce`. Server-side fields like * {@link ContainerCustomizationBase.children | `children`} and * {@link ContainerCustomizationBase.load | `load`} are typically left * absent on publication and populated by the host when the resolved * plugin appears in {@link SessionState.customizations}. * * @category Customization Types */ export interface ClientPluginCustomization extends PluginCustomization { /** Opaque version token used by the host to detect changes. */ nonce?: string; /** * Explicit enablement decisions for children this plugin contributes, * keyed by child name (for MCP servers, the server name as it appears in * the bundled `.mcp.json`). * * Bundled children are discovered by the host rather than published by the * client, so the client cannot attach `enablement` to them directly. This * carries the client's global decision for each one; the host applies it * under the child's durable key. */ childEnablement?: Record; } /** * A directory the host watches for this session. * * Presence in the customization list signals that the host may discover * customizations from this directory. When `writable` is `true`, clients * MAY persist new customizations into the directory using * [`resourceWrite`](/reference/common#resourcewrite); the host will * then surface the resulting child via the customization actions. * * The directory may not yet exist on disk. * * @category Customization Types */ export interface DirectoryCustomization extends ContainerCustomizationBase { type: CustomizationType.Directory; /** Whether this container is currently enabled. */ enabled: boolean; /** Which child customization type this directory holds. */ contents: ChildCustomizationType; /** Whether clients may write into this directory. */ writable: boolean; } /** * Fields shared by the leaf child customizations that live inside a * container — {@link AgentCustomization}, {@link SkillCustomization}, * {@link PromptCustomization}, {@link RuleCustomization}, and * {@link HookCustomization}. * * {@link McpServerCustomization} is also a child but does not extend this * base because it can appear as a top-level customization too. * * @category Customization Types */ interface ChildCustomizationBase extends CustomizationBase { /** * Whether this child is individually enabled. Absent means enabled, so a * producer only needs to set it to surface a child that exists but is * turned off on its own. * * This flag is independent of the parent container's: the **effective** * enabled state of a plugin child is the plugin's derived enabled value and * `(child.enabled ?? true)`, so a disabled plugin disables every child * regardless of each child's own flag. A directory child instead uses the * directory's `enabled` value and its own flag. * * A child is turned on or off by id with * {@link SessionCustomizationToggledAction | `session/customizationToggled`}. */ enabled?: boolean; } /** * A custom agent contributed by a plugin or directory. * * Mirrors the [Open Plugins agent](https://open-plugins.com/agent-builders/components/agents) * format: a markdown file with YAML frontmatter, where the body is the * agent's system prompt. * * @category Customization Types */ export interface AgentCustomization extends ChildCustomizationBase { type: CustomizationType.Agent; /** * Short description of what the agent specializes in and when to * invoke it. Sourced from the agent file's frontmatter `description`. */ description?: string; /** * Model the agent is pinned to, sourced from the agent file's * frontmatter `model`. Absent means the agent inherits the session's * default model. */ model?: string; /** * Allowlist of tool names the agent is scoped to, sourced from the * agent file's frontmatter `tools`. A non-empty list restricts the * agent to exactly those tools. Absent — or an empty list — imposes no * restriction beyond the session default: the agent may use any * available tool. Producers express "no restriction" by omitting the * field rather than sending an empty array, so an empty list carries no * meaning distinct from absence. */ tools?: string[]; /** * When `true`, the agent will not auto-delegate to this custom agent * as a sub-agent; it can only be selected by the user. Absent or * `false` means the agent may delegate to it. */ disableModelInvocation?: boolean; /** * When `true`, the user cannot select this custom agent (for example, * in a picker); it remains available for the agent to auto-delegate * to. Absent or `false` means the user may select it. */ disableUserInvocation?: boolean; } /** * A skill contributed by a plugin or directory. * * Covers both [Open Plugins skill formats](https://open-plugins.com/agent-builders/components/skills) * — the `skills/` directory layout (one subdirectory per skill, each with * a `SKILL.md`) and the flatter `commands/` directory of slash-command * skills. * * @category Customization Types */ export interface SkillCustomization extends ChildCustomizationBase { type: CustomizationType.Skill; /** * Short description used for help text and auto-invocation matching. * Sourced from the skill's frontmatter `description`. */ description?: string; /** * When `true`, only the user can invoke this skill — the agent will not * auto-invoke it. Sourced from the command skill's frontmatter * `disable-model-invocation` flag. */ disableModelInvocation?: boolean; /** * When `true`, the user cannot directly invoke this skill (for example, * as a slash command); it remains available for the agent to * auto-invoke. Absent or `false` means the user may invoke it. */ disableUserInvocation?: boolean; } /** * A prompt contributed by a plugin or directory. * * @category Customization Types */ export interface PromptCustomization extends ChildCustomizationBase { type: CustomizationType.Prompt; /** Short description of what the prompt does. */ description?: string; } /** * A rule contributed by a plugin or directory. * * Mirrors the [Open Plugins rule](https://open-plugins.com/agent-builders/components/rules) * format: a markdown file (e.g. `.mdc`) whose body is injected into * context while the rule is active. This type also covers tool-specific * "instruction" formats (e.g. VS Code Copilot's * `.github/instructions/*.md`), which differ only in naming — they * share the same semantics of `description`, optional always-on * activation, and optional glob scoping. * * @category Customization Types */ export interface RuleCustomization extends ChildCustomizationBase { type: CustomizationType.Rule; /** * Description of what the rule enforces. */ description?: string; /** * When `true`, the rule is always active (subject to `globs` if any). * When `false` or absent, the agent or user decides whether to apply * the rule. */ alwaysApply?: boolean; /** * Glob patterns the rule applies to. When present, the rule is only * active for matching files. */ globs?: string[]; } /** * A hook manifest contributed by a plugin or directory. * * @category Customization Types */ export interface HookCustomization extends ChildCustomizationBase { type: CustomizationType.Hook; } /** * An MCP server contributed by a plugin or directory. * * When the server is declared inline in the containing plugin manifest, * `uri` points at the manifest file and * {@link CustomizationBase.range | `range`} narrows it to the * declaration's span. * * The MCP server customization also reflects its current status. * * @category Customization Types */ export interface McpServerCustomization extends CustomizationBase { type: CustomizationType.McpServer; /** * Explicit enablement decisions for this customization, one entry per scope * that has one. This is a wire contract: producers MUST publish entries * sorted by descending specificity (Session, Workspace, then Global). * The agent host emits at most one Workspace entry, for the session's primary * working directory. Consumers MAY treat * `enablement[0]` as the decisive decision and * `enablement?.[0]?.enabled ?? true` as the effective enabled value. An * absent or empty array means no explicit decision exists, so the * customization is enabled by default. * * Flows in both directions. A client publishes this alongside a customization * to assert its global decision, which is authoritative for the Global scope; * a client always includes its global entry, even when enabled. The host * publishes the fully resolved set across all scopes, and consumers derive * the effective enabled value from that set. */ enablement?: CustomizationEnablement[]; /** * Current lifecycle state of the MCP server. */ state: McpServerState; /** * An `mcp://`-protocol channel the client uses to side-channel traffic * into the upstream MCP server itself. The channel is NOT a fresh raw MCP * connection: it piggybacks on the AHP transport * and skips the MCP `initialize` sequence. * * The agent host MAY only serve a subset of MCP on this * channel; the served subset is described by domain-specific * capabilities such as those in * {@link McpServerCustomizationApps.capabilities}. * * The channel URI SHOULD be stable across the server's lifetime, but * the agent host MAY change it (for example across a restart) and * MAY only expose it while the server is in * {@link McpServerStatus.Ready | `Ready`}. Absence means no * side-channel is currently available. */ channel?: URI; /** * MCP App support. This property SHOULD be advertised for MCP servers * which support apps. */ mcpApp?: McpServerCustomizationApps; } /** * Information from the agent host needed to render MCP Apps served * by this MCP server. * * @category MCP Server State */ export interface McpServerCustomizationApps { /** * The subset of MCP App * [`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx) * the AHP host can satisfy for Views backed by this server. The * client feeds these straight through into the `hostCapabilities` of * the `ui/initialize` response delivered to the View. */ capabilities: AhpMcpUiHostCapabilities; } /** * The subset of MCP App * [`HostCapabilities`](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx) * an AHP host can derive from the upstream MCP server (and from AHP's own * forwarding plumbing). Advertised on * {@link McpServerCustomizationApps.capabilities} so clients can pass it * through into the `hostCapabilities` of the `ui/initialize` response * delivered to an MCP App View. * * Field names mirror the MCP Apps spec exactly, so the AHP-side producer * can pass them straight through into the `hostCapabilities` of the * `ui/initialize` response delivered to the View. * * Capabilities outside this set (`openLinks`, `downloadFile`, `sandbox`, * `experimental`) are decided locally by whichever AHP client renders the * View and are NOT part of this AHP-level advertisement — only the * server-derived subset is. * * An agent host MUST only advertise a capability when it actually accepts the * corresponding methods/notifications on the `mcp://` channel: * * - {@link serverTools}: host proxies `tools/list` and `tools/call` to * the MCP server. When `listChanged` is `true`, the host also forwards * `notifications/tools/list_changed`. * - {@link serverResources}: host proxies `resources/read`, * `resources/list`, and `resources/templates/list` to the MCP server. * When `listChanged` is `true`, the host also forwards * `notifications/resources/list_changed`. * - {@link logging}: host accepts `notifications/message` log entries * from the App and forwards them via `mcpNotification` (and forwards * `logging/setLevel` calls to the server). * - {@link sampling}: host serves `sampling/createMessage` via * `mcpMethodCall`. When `sampling.tools` is present, the host also * accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content blocks * inside `CreateMessageRequest`. * * @category MCP Server State * @see {@link https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx | MCP Apps spec (SEP-1865)} */ export interface AhpMcpUiHostCapabilities { /** Producer proxies the MCP `tools/*` methods to the upstream server. */ serverTools?: { /** Producer forwards `notifications/tools/list_changed` from the server. */ listChanged?: boolean; }; /** Producer proxies the MCP `resources/*` methods to the upstream server. */ serverResources?: { /** Producer forwards `notifications/resources/list_changed` from the server. */ listChanged?: boolean; }; /** Producer accepts `notifications/message` log entries from the App via `mcpNotification`. */ logging?: Record; /** Producer serves `sampling/createMessage` via `mcpMethodCall`. */ sampling?: { /** * Producer accepts SEP-1577 `tools` / `toolChoice` / `tool_use` content * blocks inside `CreateMessageRequest`. */ tools?: Record; }; } /** * Child customizations that live inside a {@link PluginCustomization} or * {@link DirectoryCustomization}. * * @category Customization Types */ export type ChildCustomization = | AgentCustomization | SkillCustomization | PromptCustomization | RuleCustomization | HookCustomization | McpServerCustomization; /** * A top-level customization active in a session. Either a container * ({@link PluginCustomization} or {@link DirectoryCustomization}) whose * leaf customizations live in its * {@link ContainerCustomizationBase.children | `children`} array, or a * bare {@link McpServerCustomization} surfaced directly by the host. * * @category Customization Types */ export type Customization = | PluginCustomization | DirectoryCustomization | McpServerCustomization; // ─── MCP Server State ──────────────────────────────────────────────────────── /** * Discriminant for the {@link McpServerState} union. * * @category MCP Server State * @nonexhaustive */ export const enum McpServerStatus { /** Server has been registered but is not yet running. */ Starting = 'starting', /** Server is running and serving requests. */ Ready = 'ready', /** * Server is reachable but requires additional authentication before it * can start, or before it can serve a particular request. Carries the * RFC 9728 Protected Resource Metadata the client needs to obtain a * token; the client then pushes the token via the existing * `authenticate` command. */ AuthRequired = 'authRequired', /** Server failed to start, crashed, or otherwise transitioned to a fatal error. */ Error = 'error', /** Server has been shut down. */ Stopped = 'stopped', } /** * Why an MCP server is currently in the {@link McpServerStatus.AuthRequired} * state. Mirrors the three failure modes defined by the * [MCP authorization spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization.md). * * @category MCP Server State * @nonexhaustive */ export const enum McpAuthRequiredReason { /** No token has been provided yet (HTTP 401, no prior token). */ Required = 'required', /** A previously valid token expired or was revoked (HTTP 401). */ Expired = 'expired', /** * Step-up auth: a token is present but its scopes are insufficient for * the requested operation (HTTP 403 with * `WWW-Authenticate: Bearer error="insufficient_scope"`). * * Unlike {@link Required} and {@link Expired} — which typically surface * before any tool work is in flight — `InsufficientScope` is almost * always triggered by an MCP request issued mid-turn (a `tools/call`, * `resources/read`, etc.). The host SHOULD pair the * {@link McpServerAuthRequiredState} transition with * {@link SessionStatus.InputNeeded} on * {@link SessionSummary.status | the session} so the activity becomes * visible at the session-summary level, and clients SHOULD watch for * this kind on any * {@link McpServerCustomization | MCP server} backing a running tool * call so they can present an explicit "grant more access" affordance * tied to the blocked tool call. */ InsufficientScope = 'insufficientScope', } /** * Server is registered with the host but has not yet started. * * @category MCP Server State */ export interface McpServerStartingState { kind: McpServerStatus.Starting; } /** * Server is running and serving requests. * * @category MCP Server State */ export interface McpServerReadyState { kind: McpServerStatus.Ready; } /** * A pre-registered OAuth client that clients use instead of dynamic client * registration when resolving an MCP authentication challenge. * * @category MCP Server State */ export interface McpOAuthClient { /** OAuth client identifier registered with the authorization server. */ clientId: string; /** * OAuth client secret for a confidential client. Absence means the client is * public and uses a secretless flow such as authorization code with PKCE. */ clientSecret?: string; } /** * Reusable MCP authentication challenge — the RFC 9728 discovery info a * client needs to obtain a token and push it via the `authenticate` command. * Deliberately carries **no token**: this describes what is being asked for, * never the bearer token itself. * * Shared by two independent state machines that describe the same OAuth * challenge from different vantage points: * * - {@link McpServerAuthRequiredState} — the MCP server itself cannot serve * *any* request until the client authenticates. * - {@link ToolCallAuthRequiredState} — a specific in-flight tool call is * paused pending authentication (typically * {@link McpAuthRequiredReason.InsufficientScope} step-up auth * mid-execution). The server state and the tool-call state remain * separate on purpose: the server saying "I need auth" and a tool * invocation saying "I am waiting on that auth" are different facts that * can be true independently. * * @category MCP Server State */ export interface McpAuthRequirement { /** Why authentication is required. */ reason: McpAuthRequiredReason; /** * Pre-registered OAuth client to use for authorization. When present, clients * MUST use these credentials instead of dynamic client registration. */ oauthClient?: McpOAuthClient; /** * RFC 9728 Protected Resource Metadata. The `resource` field is the * canonical MCP server URI per RFC 8707, used as the OAuth `resource` * indicator. `authorization_servers` is REQUIRED by the MCP * authorization spec. */ resource: ProtectedResourceMetadata; /** * Scopes required for the current challenge, parsed from the * `WWW-Authenticate: Bearer scope="…"` header (or `scopes_supported` * fallback). Authoritative for the next authorization request — clients * MUST NOT assume any subset/superset relationship to * `resource.scopes_supported`. */ requiredScopes?: string[]; /** Human-readable hint, typically from the OAuth `error_description`. */ description?: string; } /** * Server is reachable but cannot serve requests until the client * authenticates. Mirrors the discovery flow defined by * [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) * (Protected Resource Metadata) and the OAuth 2.1 / RFC 6750 challenge * semantics required by the MCP authorization spec. * * Clients react to this state by calling the existing `authenticate` * command with the {@link ProtectedResourceMetadata.resource | resource} * carried here. There is **no** `notify/authRequired` notification for * MCP servers — the action stream is the single source of truth. * * When the transition is triggered by a request issued during a turn * — most commonly * {@link McpAuthRequiredReason.InsufficientScope | `InsufficientScope`} * surfacing mid-tool-call — the host SHOULD also raise * {@link SessionStatus.InputNeeded} on the session so the block is * visible at the summary level. Clients SHOULD watch this status on * any MCP server backing a running tool call and surface an explicit * affordance (e.g. a "grant additional access" prompt) tied to that * tool call, rather than relying on the user to notice the * customization’s status badge. * * @category MCP Server State */ export interface McpServerAuthRequiredState extends McpAuthRequirement { kind: McpServerStatus.AuthRequired; } /** * Server failed to start, crashed, or otherwise transitioned to a * non-recoverable error. Use {@link McpServerStatus.AuthRequired} * for authentication failures. * * @category MCP Server State */ export interface McpServerErrorState { kind: McpServerStatus.Error; /** Error details. */ error: ErrorInfo; } /** * Server has been shut down. The host MAY remove the server from the * session entirely shortly after this state. * * @category MCP Server State */ export interface McpServerStoppedState { kind: McpServerStatus.Stopped; } /** * Discriminated union of all MCP server lifecycle states. * Discriminated by `kind` (a {@link McpServerStatus} value). * * @category MCP Server State */ export type McpServerState = | McpServerStartingState | McpServerReadyState | McpServerAuthRequiredState | McpServerErrorState | McpServerStoppedState;