From 0d9b68f8b64fe6786e09fd08460469eb08733cf1 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Sat, 14 Nov 2026 15:51:22 +0100 Subject: [PATCH] feat: update to schema v1.32.0 and v2.0.0-alpha.16 Regenerate the v1 and v2 bindings. The v2 stable entrypoint gains: - The `replay_complete` session update and `SessionUpdate.isReplayComplete`. - Subagents: `subagent_update`, `session_message`, `session_message_chunk`, the `unknown` state, `subagent` background work, and `session` tool call content, with their guards. In v1 they still need `/unstable`. The unstable entrypoints gain: - Prompt suggestions: the `prompt_suggestions_update` session update and the `session.promptSuggestions` client capability (v1 and v2). - Skills (v2): `skill` command entries, `skill` content blocks, and the `session.prompt.skill` and `session.commands.skill` capabilities. - The `speed` config option category. - `configOptions` on `session/fork`. - `path` on markdown plans. The `file` plan type is removed. The dual-version example agent ends a v2 history replay with `replay_complete`, and holds live updates and permission requests until it is sent. Handler docs cover `replay_complete`, idempotent `auth/logout`, repeated `tool_call/cancel` and `terminal/cancel`, and `session/options` before a fork. --- MIGRATION_STABLE_UNSTABLE.md | 13 +- schema/v1/schema.unstable.json | 219 ++++++-- schema/v2/schema.json | 476 ++++++++++++++++- schema/v2/schema.unstable.json | 539 +++++++++++++++++--- scripts/generate.js | 4 +- src/examples/README.md | 2 +- src/examples/dual-version-agent.ts | 120 +++-- src/v1/acp.test.ts | 257 ++++++++++ src/v1/acp.ts | 11 +- src/v1/unstable/schema/index.ts | 7 +- src/v1/unstable/schema/types.gen.ts | 294 +++++++++-- src/v1/unstable/schema/zod.gen.ts | 179 ++++++- src/v2/acp.test.ts | 734 +++++++++++++++++++++++++++ src/v2/acp.ts | 58 ++- src/v2/schema/guards.gen.ts | 120 ++++- src/v2/schema/index.ts | 9 + src/v2/schema/types.gen.ts | 366 ++++++++++++- src/v2/schema/zod.gen.ts | 241 ++++++++- src/v2/stable/acp.ts | 49 +- src/v2/unstable/schema/guards.gen.ts | 89 +++- src/v2/unstable/schema/index.ts | 15 +- src/v2/unstable/schema/types.gen.ts | 646 +++++++++++++++++++---- src/v2/unstable/schema/zod.gen.ts | 435 +++++++++++++--- 23 files changed, 4453 insertions(+), 430 deletions(-) diff --git a/MIGRATION_STABLE_UNSTABLE.md b/MIGRATION_STABLE_UNSTABLE.md index cefa6ee7..052dc0e5 100644 --- a/MIGRATION_STABLE_UNSTABLE.md +++ b/MIGRATION_STABLE_UNSTABLE.md @@ -117,12 +117,19 @@ of APIs that require `/unstable` include: - Turn `usage` on v1 prompt responses and usage on v2 idle state updates. - `session/options`, and `configOptions` on `session/new`, `session/load` (v1), and `session/resume`. -- Subagent capabilities and session updates, and `session` tool call content. +- V1 subagent capabilities and session updates, and `session` tool call + content. In v2 they are stable: `subagent_update`, `session_message`, + `session_message_chunk`, the `unknown` state, `subagent` background work, and + `session` tool call content are available from + `@agentclientprotocol/sdk/experimental/v2`. +- Prompt suggestions: the `prompt_suggestions_update` session update and the + `session.promptSuggestions` client capability. +- V2 skills: `skill` command entries, `skill` content blocks, and the + `session.commands.skill` and `session.prompt.skill` capabilities. - Additional plan representations and `plan_removed` updates. V1's existing stable `plan` update and v2's stable item-based `plan_update` remain available from their stable-feature entrypoints. V1's ID-based `plan_update`, and v2's - file and markdown plan representations, require the corresponding unstable - entrypoint. + markdown plan representation, require the corresponding unstable entrypoint. For each protocol version, the stable entrypoint's generated types and validators come from upstream's `schema.json` and `meta.json`. Its unstable entrypoint uses diff --git a/schema/v1/schema.unstable.json b/schema/v1/schema.unstable.json index 338ebc58..93a0618e 100644 --- a/schema/v1/schema.unstable.json +++ b/schema/v1/schema.unstable.json @@ -3799,6 +3799,11 @@ "type": "string", "const": "model_config" }, + { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nResponse speed selector, such as a fast mode toggle.", + "type": "string", + "const": "speed" + }, { "description": "Thought/reasoning level selector.", "type": "string", @@ -4882,6 +4887,22 @@ } ] }, + { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nReplaces the session's current list of prompt suggestions.\n\nAgents MUST only send this update when the Client advertised\nthe `session.promptSuggestions` Client capability.", + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "prompt_suggestions_update" + } + }, + "required": ["sessionUpdate"], + "allOf": [ + { + "$ref": "#/$defs/PromptSuggestionsUpdate" + } + ] + }, { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nAnnounces a child session created and owned by this session, or updates\nthat ownership association's metadata.", "type": "object", @@ -5160,22 +5181,6 @@ } ] }, - { - "description": "A URI pointing to a file containing the plan.", - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "file" - } - }, - "required": ["type"], - "allOf": [ - { - "$ref": "#/$defs/PlanFile" - } - ] - }, { "description": "Raw markdown content for the plan.", "type": "object", @@ -5231,31 +5236,6 @@ }, "required": ["planId", "entries"] }, - "PlanFile": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA plan represented by a file URI.", - "type": "object", - "properties": { - "planId": { - "description": "The plan ID to update.", - "allOf": [ - { - "$ref": "#/$defs/PlanId" - } - ] - }, - "uri": { - "description": "The URI of the file containing the plan.", - "type": "string" - }, - "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", - "type": ["object", "null"], - "x-deserialize-default-on-error": true, - "additionalProperties": true - } - }, - "required": ["planId", "uri"] - }, "PlanMarkdown": { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA plan represented as raw markdown content.", "type": "object", @@ -5272,6 +5252,11 @@ "description": "Markdown content for the plan.", "type": "string" }, + "path": { + "description": "The absolute path of the file that holds this plan.\n\nThis field is optional. Omitting it or sending `null` both mean that the plan\nhas no file. `content` is the plan to display whether or not `path` is set.\nClients can use `path` to open the file.", + "type": ["string", "null"], + "x-deserialize-default-on-error": true + }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", "type": ["object", "null"], @@ -5695,6 +5680,48 @@ }, "required": ["compactionId", "content"] }, + "PromptSuggestion": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA prompt the Agent predicts the user will send next.", + "type": "object", + "properties": { + "prompt": { + "description": "The content of the suggested prompt.\n\nRequired and non-null. Must contain at least one content block. This is\nthe same type as the `prompt` field of `session/prompt`, and the Client\ncan send it in that field unchanged.\n\nAgents MUST only use `text` and `resource_link` content blocks, and\nother content block types that both the Agent's prompt capabilities\nand the Client's `session.promptSuggestions` capability advertise.\nAgents MUST NOT send a prompt whose only content is empty or\nwhitespace-only text.", + "type": "array", + "items": { + "$ref": "#/$defs/ContentBlock" + }, + "minItems": 1 + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["prompt"] + }, + "PromptSuggestionsUpdate": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nReplaces the session's current list of prompt suggestions.\n\nA prompt suggestion is a prompt the Agent predicts the user will send next.\nThe Client offers suggestions where the user writes the next prompt. The\nlist in the most recent update is the current list until the Agent sends\nanother update. A session starts with an empty list.\n\nSuggestions are not part of session history. Agents SHOULD NOT include\nthis update when they replay a session.\n\nAgents MUST only send this update when the Client advertised\nthe `session.promptSuggestions` Client capability.", + "type": "object", + "properties": { + "suggestions": { + "description": "The suggestions the Client can offer, ordered most likely first.\n\nRequired and non-null. An empty list withdraws the suggestions the\nClient is offering. A Client skips an entry it cannot parse and keeps\nthe rest.", + "type": "array", + "items": { + "$ref": "#/$defs/PromptSuggestion" + }, + "x-deserialize-skip-invalid-items": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["suggestions"] + }, "SubagentSessionCapabilities": { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nClient-initiated session mutations permitted for a specific subagent session.\n\nA mutation requires an explicit per-child capability. Support for the method\non ordinary sessions does not grant support on a child.", "type": "object", @@ -6089,7 +6116,7 @@ } }, "UnknownStateUpdate": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nThe Agent cannot currently determine foreground activity.\n\nReport this when activity becomes unobservable, not merely because the child\nhas been quiet. The Client MUST stop presenting the previous state as confirmed\ncurrent activity, but may retain it as last known. A later state replaces this\nsnapshot normally.\n\nThis is not a task outcome or session closure. It does not cancel work, resolve\npending requests, or revoke capabilities. Capabilities are updated separately.", + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nThe Agent cannot currently determine foreground activity.\n\nReport this when activity becomes unobservable, not merely because the child\nhas been quiet. The Client MUST stop presenting the previous state as confirmed\ncurrent activity, but may retain it as last known. A later state replaces this\nsnapshot normally.\n\nThis is not a task outcome or session closure. It does not cancel work, resolve\npending requests, or revoke capabilities. If a control is no longer available,\nthe Agent updates the capabilities explicitly. Unfinished tool calls and\nterminals stay as last reported. The Agent does not invent a final status\nfor them, and Clients do not present them as confirmed live activity.", "type": "object", "properties": { "_meta": { @@ -6780,6 +6807,18 @@ ], "x-deserialize-default-on-error": true }, + "promptSuggestions": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nSupport for `prompt_suggestions_update` session updates.\n\nOptional. Omitted or `null` both mean the client does not advertise support.\nSupplying `{}` means the client can offer prompt suggestions made of\n`text` and `resource_link` content blocks.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSuggestionsCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", "type": ["object", "null"], @@ -6832,6 +6871,90 @@ "description": "Client support for presenting live notices to the user.", "type": "object" }, + "PromptSuggestionsCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nClient support for `prompt_suggestions_update` session updates.\n\nSupplying `{}` means the client can offer prompt suggestions made of `text`\nand `resource_link` content blocks. The client opts in to other content\nblock types with the fields below. They have the same names as the agent's\nprompt capabilities.", + "type": "object", + "properties": { + "image": { + "description": "Whether prompt suggestions may include `image` content blocks.\n\nOptional. Omitted or `null` both mean the client does not advertise support.\nSupplying `{}` means the client can offer suggestions that include images.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSuggestionsImageCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "audio": { + "description": "Whether prompt suggestions may include `audio` content blocks.\n\nOptional. Omitted or `null` both mean the client does not advertise support.\nSupplying `{}` means the client can offer suggestions that include audio.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSuggestionsAudioCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "embeddedContext": { + "description": "Whether prompt suggestions may include `resource` content blocks.\n\nOptional. Omitted or `null` both mean the client does not advertise support.\nSupplying `{}` means the client can offer suggestions that include\nembedded resources.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSuggestionsEmbeddedContextCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSuggestionsImageCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapabilities for image content in prompt suggestions.\n\nSupplying `{}` means the client can offer suggestions that include images.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSuggestionsAudioCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapabilities for audio content in prompt suggestions.\n\nSupplying `{}` means the client can offer suggestions that include audio.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSuggestionsEmbeddedContextCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapabilities for embedded resources in prompt suggestions.\n\nSupplying `{}` means the client can offer suggestions that include\n`resource` content blocks.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, "SubagentCapabilities": { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapability marker for exposing reusable child sessions as restricted ACP sessions.\n\nSupplying `{}` advertises support for child association and state updates,\nsession-directed messages, and restricted-session semantics. The client\nmust advertise this capability before the agent sends subagent updates or\nsession-directed messages.", "type": "object", @@ -7100,7 +7223,7 @@ ] }, "configOptions": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial values for the session's config options.\n\nOptional. Omitted and `null` are equivalent to an empty list. Clients MUST only send\nthis when the Agent advertises `sessionCapabilities.options`.\n\nThe Agent applies the entries in list order, as if the Client had called\n`session/set_config_option` for each entry after the session was set up. Malformed\nentries and duplicate `configId` values fail the request with `-32602`. The Agent MUST\nignore an entry whose `configId` is unknown, whose `type` does not match the option,\nor whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually\nhas. The Agent SHOULD NOT send `config_option_update` notifications for these entries.", + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial values for the session's config options.\n\nOptional. Omitted and `null` are equivalent to an empty list. Clients MUST only send\nthis when the Agent advertises `sessionCapabilities.options`.\n\nThe Agent applies the entries in list order, as if the Client had called\n`session/set_config_option` for each entry after the session was set up. Malformed\nentries and duplicate `configId` values fail the request with `-32602`. The Agent MUST\nignore an entry whose `configId` is unknown, whose `type` does not match the option,\nor whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually\nhas. The Agent SHOULD NOT send `config_option_update` notifications for these entries,\nand SHOULD NOT send other `session/update` notifications caused by applying them\nbefore it responds.", "type": "array", "items": { "$ref": "#/$defs/InitialSessionConfigOption" @@ -7609,6 +7732,14 @@ } ] }, + "configOptions": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial values for the forked session's config options.\n\nOptional. Omitted and `null` are equivalent to an empty list. Clients MUST only send\nthis when the Agent advertises `sessionCapabilities.options`.\n\nThe Agent applies the entries in list order on top of the configuration the fork\nstarts with, as if the Client had called `session/set_config_option` for each entry\nafter the session was set up. Malformed entries and duplicate `configId` values fail\nthe request with `-32602`. The Agent MUST ignore an entry whose `configId` is unknown,\nwhose `type` does not match the option, or whose value is not allowed when it is\napplied. The `configOptions` in the response contain the values the session actually\nhas. The Agent SHOULD NOT send `config_option_update` notifications for these entries,\nand SHOULD NOT send other `session/update` notifications caused by applying them\nbefore it responds.", + "type": "array", + "items": { + "$ref": "#/$defs/InitialSessionConfigOption" + }, + "x-deserialize-default-on-null": true + }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Implementations MUST NOT make assumptions about values at\nthese keys.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility)", "type": ["object", "null"], @@ -7690,7 +7821,7 @@ "x-method": "session/status" }, "SessionOptionsRequest": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nRequest parameters for `session/options`, which returns the config options a session\nwould start with, without creating or resuming a session.\n\nOnly available if the Agent supports the `sessionCapabilities.options` capability.\n\n`session/options` MUST NOT create or resume a session, send `session/update`\nnotifications, add an entry to `session/list`, or change an existing session. Agents\nSHOULD NOT connect to MCP servers to answer it.", + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nRequest parameters for `session/options`, which returns the config options a session\nwould start with, without creating or resuming a session.\n\nOnly available if the Agent supports the `sessionCapabilities.options` capability.\n\n`session/options` MUST NOT create or resume a session, send `session/update`\nnotifications, add an entry to `session/list`, or change an existing session. Agents\nSHOULD NOT connect to MCP servers to answer it. Agents MAY start their runtime to resolve\nthe options. The rules above still hold for it.", "type": "object", "properties": { "cwd": { diff --git a/schema/v2/schema.json b/schema/v2/schema.json index 21810a43..ed0a1dd0 100644 --- a/schema/v2/schema.json +++ b/schema/v2/schema.json @@ -572,6 +572,22 @@ } ] }, + { + "description": "A display-only reference to another session, such as a subagent.", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session" + } + }, + "required": ["type"], + "allOf": [ + { + "$ref": "#/$defs/SessionReference" + } + ] + }, { "title": "other", "description": "Custom or future tool call content.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nReceivers that do not understand this content type should preserve the\nraw payload when storing, replaying, proxying, or forwarding tool call\noutput, and otherwise ignore it or display it generically.", @@ -614,6 +630,16 @@ } }, "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "session" + } + }, + "required": ["type"] } ] }, @@ -1586,6 +1612,27 @@ }, "required": ["terminalId"] }, + "SessionReference": { + "description": "A display-only reference to another session that this tool call operates on,\nsuch as a subagent it created, delegated to, or is waiting on.\n\nThe referenced session must already be known to the Client through ordinary\nsession setup or a `subagent_update` announcement. The reference does not\nestablish ownership, grant any Client-initiated controls, or report that the\nreferenced session received or finished any work.", + "type": "object", + "properties": { + "sessionId": { + "description": "The referenced session.", + "allOf": [ + { + "$ref": "#/$defs/SessionId" + } + ] + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["sessionId"] + }, "ToolCallLocation": { "description": "A file location being accessed or modified by a tool.\n\nEnables clients to implement \"follow-along\" features that track\nwhich files the agent is working with in real-time.\n\nSee protocol docs: [Following the Agent](https://agentclientprotocol.com/protocol/v2/tool-calls#following-the-agent)", "type": "object", @@ -3773,7 +3820,7 @@ }, { "title": "other", - "description": "Custom or future command kind.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nClients that do not understand this command type should preserve the raw\npayload when storing, replaying, proxying, or forwarding command\nmetadata, and otherwise ignore the command or display it as a plain\ncommand using its `name` and `description`.", + "description": "Custom or future command kind.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nClients that do not understand this command type should preserve the raw\npayload when storing, replaying, proxying, or forwarding command\nmetadata, and otherwise ignore the command. They MUST NOT display it as a\nslash command.", "type": "object", "properties": { "type": { @@ -3781,7 +3828,7 @@ "type": "string" }, "name": { - "description": "Command name without the leading `/`. MUST NOT contain whitespace and MUST be unique\nwithin the command list.", + "description": "Command name. MUST NOT contain whitespace and MUST be unique within the command list.", "type": "string" }, "description": { @@ -4702,6 +4749,70 @@ } ] }, + { + "description": "The history replay requested by `session/resume` is complete for this session.\n\nEvery update for the session between the `session/resume` request and\nthis update is replayed history. Every update after it is not.", + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "replay_complete" + } + }, + "required": ["sessionUpdate"], + "allOf": [ + { + "$ref": "#/$defs/ReplayComplete" + } + ] + }, + { + "description": "Announces a child session created and owned by this session, or updates\nthat ownership association's metadata.", + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "subagent_update" + } + }, + "required": ["sessionUpdate"], + "allOf": [ + { + "$ref": "#/$defs/SubagentUpdate" + } + ] + }, + { + "description": "A message upsert observed in this session's transcript, sent to or\nreceived from another session.", + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "session_message" + } + }, + "required": ["sessionUpdate"], + "allOf": [ + { + "$ref": "#/$defs/SessionMessage" + } + ] + }, + { + "description": "One content block appended to a sent or received session message.", + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "session_message_chunk" + } + }, + "required": ["sessionUpdate"], + "allOf": [ + { + "$ref": "#/$defs/SessionMessageChunk" + } + ] + }, { "title": "other", "description": "Custom or future session update.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nReceivers that do not understand this update type should preserve the\nraw payload when storing, replaying, proxying, or forwarding session\nhistory, and otherwise ignore it or display it generically.", @@ -4904,6 +5015,46 @@ } }, "required": ["sessionUpdate"] + }, + { + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "replay_complete" + } + }, + "required": ["sessionUpdate"] + }, + { + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "subagent_update" + } + }, + "required": ["sessionUpdate"] + }, + { + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "session_message" + } + }, + "required": ["sessionUpdate"] + }, + { + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "session_message_chunk" + } + }, + "required": ["sessionUpdate"] } ] }, @@ -5065,6 +5216,22 @@ } ] }, + { + "description": "A detached subagent session that has anything running: its state is not `idle`, or\nits own `background` list is non-empty or not reported.", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "subagent" + } + }, + "required": ["type"], + "allOf": [ + { + "$ref": "#/$defs/BackgroundSubagent" + } + ] + }, { "title": "other", "description": "Custom or future background work.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nAn unknown item still means background work is running, even when the Client\ncannot show what it is.", @@ -5097,6 +5264,16 @@ } }, "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "subagent" + } + }, + "required": ["type"] } ] }, @@ -5146,6 +5323,27 @@ }, "required": ["toolCallId"] }, + "BackgroundSubagent": { + "description": "A listed subagent session associated with this session through `subagent_update`.", + "type": "object", + "properties": { + "sessionId": { + "description": "The ID of the listed session.", + "allOf": [ + { + "$ref": "#/$defs/SessionId" + } + ] + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["sessionId"] + }, "RunningStateUpdate": { "description": "Foreground work is in progress.", "type": "object", @@ -5389,6 +5587,26 @@ } } }, + "UnknownStateUpdate": { + "description": "The Agent cannot currently determine foreground activity.\n\nReport this when activity becomes unobservable, not merely because the session\nhas been quiet. The Client MUST stop presenting the previous state as confirmed\ncurrent activity, but may retain it as last known. A later state replaces this\nsnapshot normally.\n\nThis is not a task outcome or session closure. It does not cancel work, resolve\npending requests, or revoke capabilities. If a control is no longer available,\nthe Agent updates the capabilities explicitly. Unfinished tool calls and\nterminals stay as last reported. The Agent does not invent a final status\nfor them, and Clients do not present them as confirmed live activity.", + "type": "object", + "properties": { + "background": { + "description": "Work running independently of foreground work, as a complete snapshot.\n\nOptional. Omitted and `null` are equivalent and mean the Agent is not reporting\nbackground work, not that nothing is running. `[]` means no background work is\nrunning. An Agent that reports background work MUST include it on every state\nupdate for the session. A malformed list is treated as not reported.\n\nSee protocol docs: [Background Work](https://agentclientprotocol.com/protocol/v2/prompt-lifecycle#background-work)", + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/BackgroundWork" + }, + "x-deserialize-default-on-error": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, "StateUpdate": { "description": "A snapshot of the session's aggregate foreground activity.\n\nForeground work is the agent's active response or workflow in the session,\nincluding model processing, tool execution, and waiting for user action needed\nto continue that work. It can start with a prompt or be agent-initiated, for\nexample to pursue an ongoing goal or follow up on a completed background task.\nEither way, the agent MUST report `running` when foreground work starts,\nor `requires_action` if it starts blocked on user action, and `idle` when it\nends. The agent SHOULD include a stop reason on that idle update.\nAgent-initiated work does not require a prompt request or a synthetic user\nmessage.\n\n`running` may precede user-message insertion, for example during a foreground\nhook. It is not acknowledgment of insertion, and rejecting a prompt does not\nitself end foreground work.\n\nBackground activity can continue and emit other `session/update` notifications\nwhile `idle`. Those notifications, including a detached task's progress or\ncompletion, do not by themselves change this state. A permission request only\nwarrants `requires_action` when foreground work is blocked on user action.\n\nThe agent SHOULD report `requires_action` when the foreground workflow as a\nwhole cannot progress without user action, including permissions or elicitations.\nOnce reported, it MUST report `running` when progress resumes, or `idle` when\nwork ends without resuming.\nIf other foreground work can progress, the state remains `running`. Resolving\none of several requests does not itself change the state. Agents SHOULD ignore\nresponses for work that has already ended rather than use them to resume it.\n\nA new session defaults to idle until a state update reports otherwise.\nResume MUST send a current-state snapshot after any replay and before its\nresponse. The snapshot reports foreground state after resuming.\nThe response alone does not imply idle.\nRepeated snapshots are idempotent, not additional work, completions, or usage.\nA repeated state may carry updated information without another state transition.\nThe agent MUST report `idle` when no foreground activity remains. Completing\nan individual activity while other foreground work continues does not require\nan idle boundary. Local commands use the same lifecycle even without model work.\n\nSee protocol docs: [Session States](https://agentclientprotocol.com/protocol/v2/prompt-lifecycle#session-states)", "anyOf": [ @@ -5440,6 +5658,22 @@ } ] }, + { + "description": "The Agent cannot currently determine foreground activity.\nThis replaces previously confirmed activity without ending the work or session.", + "type": "object", + "properties": { + "state": { + "type": "string", + "const": "unknown" + } + }, + "required": ["state"], + "allOf": [ + { + "$ref": "#/$defs/UnknownStateUpdate" + } + ] + }, { "title": "other", "description": "Custom or future session state.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nUnless the Client advertised support for this state, Agents MUST still\nreport the standard states.\nClients MUST NOT treat an unrecognized state as `idle` or present the\nprevious state as confirmed, and show a generic busy state until a known\nstate arrives.", @@ -5482,6 +5716,16 @@ } }, "required": ["state"] + }, + { + "type": "object", + "properties": { + "state": { + "type": "string", + "const": "unknown" + } + }, + "required": ["state"] } ] }, @@ -6218,6 +6462,234 @@ }, "required": ["compactionId", "content"] }, + "ReplayComplete": { + "description": "Marks the end of the history replay requested by `session/resume`.\n\nThe Agent sends this once on a session for each `session/resume` request\nwith a `replayFrom` cursor, after the last replayed update for that session\nand before the response. It sends it for an empty history too. Every update\nfor the session between the request and this update is replayed history.\nEvery update after it is not.\n\nThe Agent does not send it when `replayFrom` is omitted or `null`, or when\nit cannot finish the replay. A resume that ends without it delivered a\ntruncated replay.\n\nSee protocol docs: [Replay Complete](https://agentclientprotocol.com/protocol/v2/session-setup#replay-complete)", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "SubagentSessionCapabilities": { + "description": "Client-initiated session mutations permitted for a specific subagent session.\n\nA mutation requires an explicit per-child capability. Support for the method\non ordinary sessions does not grant support on a child.", + "type": "object", + "properties": { + "cancel": { + "description": "Permits the client to cancel this child's current work without ending\nthe session. Omitted and `null` are equivalent and mean unsupported. An object (including\n`{}`) means supported.", + "anyOf": [ + { + "$ref": "#/$defs/SessionCancelCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "SessionCancelCapabilities": { + "description": "Capability to cancel work in a subagent session without ending that session.\n\nSupplying `{}` advertises support. An omitted or `null` `cancel` does not.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "SubagentUpdate": { + "description": "Notification that the enclosing parent session created and owns a child session.\n\nLater updates modify the existing association's metadata, not its ownership.\n\nSent on the immediate parent session. The first update for an unknown\n`sessionId` announces the child and MUST be sent\nbefore any live request or notification bearing the child's session ID,\nincluding messages naming it as sender or recipient. Child events are\ndelivered automatically. No separate child load, resume, or subscription is needed.\nUnderstanding this update, registering child sessions, and applying their\noperation restrictions are baseline v2 requirements. No Client capability\nis required.\n\nOnly `sessionId` is required. Other fields have\npatch semantics: omitted fields leave the stored value unchanged, `null`\nclears or unsets the value, and concrete values replace it. A concrete\n`_meta` object instead patches the stored metadata by top-level key. For\n`state`, `null` removes the current state report and leaves activity\nunconfirmed. It does not report idle or cancellation. A child whose capabilities are\nunset permits no Client-initiated session mutations.\n\n`cwd` and `additionalDirectories` report where the Agent placed the child.\nWhile unset, each inherits the immediate parent's effective value.\nThe title and description provide the parent's display metadata for the\nchild. Agents SHOULD mirror known child state changes in the parent's\n`state` using the same `StateUpdate` snapshot as the\nordinary `state_update` notification on the child session. This reports\nchild state, not a separate parent-owned lifecycle. Consumers should\ntreat duplicate reports idempotently.\nCompleting or cancelling work does not end the association: the parent may\nmessage the same child again. Individual messages and their outcomes do not\nchange this association.", + "type": "object", + "properties": { + "sessionId": { + "description": "The opaque session ID identifying the child in all ACP messages.", + "allOf": [ + { + "$ref": "#/$defs/SessionId" + } + ] + }, + "title": { + "description": "The parent's human-readable display title for this child. It need not be unique.\n\nOptional and nullable. Omitted means unchanged. `null` clears it. If no\ntitle is set, the Client chooses a fallback presentation.", + "type": ["string", "null"], + "x-deserialize-default-on-error": true + }, + "description": { + "description": "The parent's human-readable description of the child's role or purpose.\n\nOptional and nullable. Omitted means unchanged. `null` clears it. This is\ncurrent display metadata, not the history of instructions sent to the child.", + "type": ["string", "null"], + "x-deserialize-default-on-error": true + }, + "capabilities": { + "description": "Client-initiated session mutations permitted for this subagent session.\n\nRead-only operations retain their normal protocol semantics and\ncapability requirements.", + "anyOf": [ + { + "$ref": "#/$defs/SubagentSessionCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "state": { + "description": "The child's current foreground state, mirrored onto its parent association.\n\nOptional and nullable. Omitted means unchanged. `null` removes the\ncurrent report and leaves activity unconfirmed (not idle or cancelled).\nA concrete `StateUpdate` replaces the entire previous snapshot.", + "anyOf": [ + { + "$ref": "#/$defs/StateUpdate" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "cwd": { + "description": "The child's working directory. Must be an absolute path.\n\nOptional and nullable. Omitted means unchanged. `null` clears the\nreported value. While unset, the child inherits its immediate parent's\neffective working directory.", + "anyOf": [ + { + "$ref": "#/$defs/AbsolutePath" + }, + { + "type": "null" + } + ] + }, + "additionalDirectories": { + "description": "Additional workspace roots for the child. Each path must be absolute.\n\nOptional and nullable. Omitted means unchanged. `null` clears the\nreported value. While unset, the child inherits its immediate parent's\neffective additional directories, independently of `cwd`. A concrete\narray replaces the whole list. `[]` reports no additional directories.", + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/AbsolutePath" + } + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged. `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["sessionId"] + }, + "SessionMessage": { + "description": "An upsert for an inter-session message.", + "type": "object", + "properties": { + "messageId": { + "description": "Identifier of this message within the enclosing session's transcript.", + "allOf": [ + { + "$ref": "#/$defs/MessageId" + } + ] + }, + "senderSessionId": { + "description": "The sending session.\n\nOmitted leaves the stored value unchanged. `null` clears it. A value replaces it.", + "anyOf": [ + { + "$ref": "#/$defs/SessionId" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "recipientSessionId": { + "description": "The receiving session.\n\nOmitted leaves the stored value unchanged. `null` clears it. A value replaces it.", + "anyOf": [ + { + "$ref": "#/$defs/SessionId" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "senderMessageId": { + "description": "On an incoming view, the `messageId` of the same message's outgoing view in the\nsender's transcript.\n\nOmitted leaves the stored value unchanged. `null` clears it. A value replaces it. It can arrive\nafter the message starts streaming.", + "anyOf": [ + { + "$ref": "#/$defs/MessageId" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "recipientMessageId": { + "description": "On an outgoing view, the `messageId` of the same message's incoming view in the\nrecipient's transcript.\n\nOmitted leaves the stored value unchanged. `null` clears it. A value replaces it. It can arrive\nafter the message starts streaming.", + "anyOf": [ + { + "$ref": "#/$defs/MessageId" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "content": { + "description": "Omitted leaves content unchanged. `null` clears it. A concrete array\nreplaces the whole content collection.", + "type": ["array", "null"], + "items": { + "$ref": "#/$defs/ContentBlock" + }, + "x-deserialize-default-on-error": true, + "x-deserialize-skip-invalid-items": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted leaves the stored metadata unchanged. `null`\nclears it. An object patches the stored metadata by top-level key: keys it omits are\nunchanged, a key set to `null` is removed, and any other value replaces that key's\nstored value without merging nested objects. Beyond this patch rule, implementations\nMUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["messageId"] + }, + "SessionMessageChunk": { + "description": "A streamed content block of an inter-session message.\n\nA chunk carries only content. The message's participants and references come from\n`session_message` upserts, which can arrive before, between, or after its chunks.", + "type": "object", + "properties": { + "messageId": { + "description": "Identifier of this message within the enclosing session's transcript.", + "allOf": [ + { + "$ref": "#/$defs/MessageId" + } + ] + }, + "content": { + "description": "A single content block appended to the message.", + "allOf": [ + { + "$ref": "#/$defs/ContentBlock" + } + ] + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\nThis metadata is scoped to the chunk and does not patch stored metadata.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["messageId", "content"] + }, "CompleteElicitationNotification": { "description": "Notification sent by the agent when a URL-based elicitation is complete.\n\nThis notification is not a foreground state update.", "type": "object", diff --git a/schema/v2/schema.unstable.json b/schema/v2/schema.unstable.json index 1c555091..607fdf8e 100644 --- a/schema/v2/schema.unstable.json +++ b/schema/v2/schema.unstable.json @@ -620,7 +620,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA display-only reference to another session, such as a subagent.", + "description": "A display-only reference to another session, such as a subagent.", "type": "object", "properties": { "type": { @@ -777,6 +777,22 @@ } ] }, + { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA reference to a skill that the user invokes with this prompt.\n\nRequires the `skill` prompt capability when included in prompts. A skill\nreference has no meaning outside a user message.", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "skill" + } + }, + "required": ["type"], + "allOf": [ + { + "$ref": "#/$defs/SkillReference" + } + ] + }, { "title": "other", "description": "Custom or future content block.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nReceivers that do not understand this content block type should preserve\nthe raw payload when storing, replaying, proxying, or forwarding content,\nand otherwise ignore it or display it generically. Agents MUST reject a\n`session/prompt` request with a content block type they do not support\nwith `-32602`.", @@ -839,6 +855,16 @@ } }, "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "skill" + } + }, + "required": ["type"] } ] }, @@ -1283,6 +1309,23 @@ }, "required": ["resource"] }, + "SkillReference": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA reference to a skill that the user invokes with a prompt.\n\nThe Client sends it in `session/prompt` to invoke a `skill` entry from the\nsession's command list. The Agent MUST treat each reference as the user\nexplicitly invoking that skill for this prompt, and applies the skill to\nthe prompt as a whole. The reference carries no input.\n\nAn Agent that advertises the `session.prompt.skill` capability accepts one\nskill reference per prompt, as the first content block. An Agent that also\nadvertises `session.prompt.skill.inline` accepts any number of them, at any\nposition. Clients MUST NOT send a skill reference that the Agent's\ncapability does not allow.\n\nThe `type` discriminator value is `skill`.", + "type": "object", + "properties": { + "name": { + "description": "The `name` of a `skill` entry in the session's current command list.\n\nRequired and non-null. The Agent MUST reject the prompt with `-32602`\nwhen it cannot resolve the name to a skill the user can invoke.", + "type": "string" + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["name"] + }, "Content": { "description": "Standard content block (text, images, resources).", "type": "object", @@ -1660,7 +1703,7 @@ "required": ["terminalId"] }, "SessionReference": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA display-only reference to another session that this tool call operates on,\nsuch as a subagent it created, delegated to, or is waiting on.\n\nThe referenced session must already be known to the Client through ordinary\nsession setup or a `subagent_update` announcement. The reference does not\nestablish ownership, grant any Client-initiated controls, or report that the\nreferenced session received or finished any work.", + "description": "A display-only reference to another session that this tool call operates on,\nsuch as a subagent it created, delegated to, or is waiting on.\n\nThe referenced session must already be known to the Client through ordinary\nsession setup or a `subagent_update` announcement. The reference does not\nestablish ownership, grant any Client-initiated controls, or report that the\nreferenced session received or finished any work.", "type": "object", "properties": { "sessionId": { @@ -3323,6 +3366,18 @@ ], "x-deserialize-default-on-error": true }, + "skill": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nAgent supports `skill` content blocks.\n\nOptional. Omitted and `null` are equivalent and mean the agent does not advertise support.\nSupplying `{}` means a prompt may contain one `skill` content block, as its first block.\nAn agent that sends `skill` entries in `availableCommands` MUST advertise this capability.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSkillCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", "type": ["object", "null"], @@ -3367,6 +3422,42 @@ } } }, + "PromptSkillCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapabilities for `skill` content blocks in prompt requests.\n\nSupplying `{}` means a prompt may contain one `skill` content block, as\nits first block. The agent opts in to more with the `inline` field.", + "type": "object", + "properties": { + "inline": { + "description": "Agent supports `skill` content blocks at any position in a prompt.\n\nOptional. Omitted and `null` are equivalent and mean the agent does not advertise support.\nSupplying `{}` means a prompt may contain any number of `skill` content blocks, at any\nposition.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSkillInlineCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSkillInlineCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapabilities for `skill` content blocks at any position in a prompt.\n\nSupplying `{}` means a prompt may contain any number of `skill` content\nblocks, at any position.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, "McpCapabilities": { "description": "MCP capabilities supported by the agent for session lifecycle requests.", "type": "object", @@ -4298,6 +4389,11 @@ "type": "string", "const": "model_config" }, + { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nResponse speed selector, such as a fast mode toggle.", + "type": "string", + "const": "speed" + }, { "description": "Thought/reasoning level selector.", "type": "string", @@ -4459,9 +4555,25 @@ } ] }, + { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nAn Agent Skill the user can invoke with a `skill` content block.\n\nAgents MUST only send this entry when the Client advertised\nthe `session.commands.skill` Client capability.", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "skill" + } + }, + "required": ["type"], + "allOf": [ + { + "$ref": "#/$defs/Skill" + } + ] + }, { "title": "other", - "description": "Custom or future command kind.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nClients that do not understand this command type should preserve the raw\npayload when storing, replaying, proxying, or forwarding command\nmetadata, and otherwise ignore the command or display it as a plain\ncommand using its `name` and `description`.", + "description": "Custom or future command kind.\n\nValues beginning with `_` are reserved for implementation-specific\nextensions. Unknown values that do not begin with `_` are reserved for\nfuture ACP variants.\n\nClients that do not understand this command type should preserve the raw\npayload when storing, replaying, proxying, or forwarding command\nmetadata, and otherwise ignore the command. They MUST NOT display it as a\nslash command.", "type": "object", "properties": { "type": { @@ -4469,7 +4581,7 @@ "type": "string" }, "name": { - "description": "Command name without the leading `/`. MUST NOT contain whitespace and MUST be unique\nwithin the command list.", + "description": "Command name. MUST NOT contain whitespace and MUST be unique within the command list.", "type": "string" }, "description": { @@ -4489,6 +4601,16 @@ } }, "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "skill" + } + }, + "required": ["type"] } ] }, @@ -4594,6 +4716,39 @@ }, "required": ["name", "description"] }, + "Skill": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nAn Agent Skill the user can invoke.\n\nThe `type` discriminator value is `skill`.\n\nThe Client invokes a skill with a `skill` content block that names it. The\n`/name` syntax applies only to `command` entries. An Agent that sends\n`skill` entries MUST advertise the `session.prompt.skill` capability.\n\nAgents MUST only send `skill` entries when the Client advertised the\n`session.commands.skill` Client capability. Agents SHOULD advertise only\nskills the user can invoke.", + "type": "object", + "properties": { + "name": { + "description": "Skill name. The Client uses it to refer to the skill in a `skill` content block.\n\nMUST NOT contain whitespace and MUST be unique within the command list,\nacross commands and skills. It need not match the `name` in the skill's\nfrontmatter.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the skill does, shown to users.", + "type": "string" + }, + "path": { + "description": "The absolute path of the file that defines the skill, normally its\n`SKILL.md`, on the machine the Agent runs on.\n\nOmitted and `null` are equivalent and mean the skill has no such file,\nas with a skill that is built into the Agent or served by an MCP server.", + "anyOf": [ + { + "$ref": "#/$defs/AbsolutePath" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["name", "description"] + }, "ListSessionsResponse": { "description": "Response from listing sessions.", "type": "object", @@ -4876,7 +5031,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nThe Agent cannot currently determine foreground activity.\nThis replaces previously confirmed activity without ending the work or session.", + "description": "The Agent cannot currently determine foreground activity.\nThis replaces previously confirmed activity without ending the work or session.", "type": "object", "properties": { "state": { @@ -4986,7 +5141,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA detached subagent session that has anything running: its state is not `idle`, or\nits own `background` list is non-empty or not reported.", + "description": "A detached subagent session that has anything running: its state is not `idle`, or\nits own `background` list is non-empty or not reported.", "type": "object", "properties": { "type": { @@ -5093,7 +5248,7 @@ "required": ["toolCallId"] }, "BackgroundSubagent": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA listed subagent session associated with this session through `subagent_update`.", + "description": "A listed subagent session associated with this session through `subagent_update`.", "type": "object", "properties": { "sessionId": { @@ -5599,7 +5754,7 @@ } }, "UnknownStateUpdate": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nThe Agent cannot currently determine foreground activity.\n\nReport this when activity becomes unobservable, not merely because the session\nhas been quiet. The Client MUST stop presenting the previous state as confirmed\ncurrent activity, but may retain it as last known. A later state replaces this\nsnapshot normally.\n\nThis is not a task outcome or session closure. It does not cancel work, resolve\npending requests, or revoke capabilities. Capabilities are updated separately.", + "description": "The Agent cannot currently determine foreground activity.\n\nReport this when activity becomes unobservable, not merely because the session\nhas been quiet. The Client MUST stop presenting the previous state as confirmed\ncurrent activity, but may retain it as last known. A later state replaces this\nsnapshot normally.\n\nThis is not a task outcome or session closure. It does not cancel work, resolve\npending requests, or revoke capabilities. If a control is no longer available,\nthe Agent updates the capabilities explicitly. Unfinished tool calls and\nterminals stay as last reported. The Agent does not invent a final status\nfor them, and Clients do not present them as confirmed live activity.", "type": "object", "properties": { "background": { @@ -6212,7 +6367,39 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nAnnounces a child session created and owned by this session, or updates\nthat ownership association's metadata.", + "description": "The history replay requested by `session/resume` is complete for this session.\n\nEvery update for the session between the `session/resume` request and\nthis update is replayed history. Every update after it is not.", + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "replay_complete" + } + }, + "required": ["sessionUpdate"], + "allOf": [ + { + "$ref": "#/$defs/ReplayComplete" + } + ] + }, + { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nReplaces the session's current list of prompt suggestions.\n\nAgents MUST only send this update when the Client advertised\nthe `session.promptSuggestions` Client capability.", + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "prompt_suggestions_update" + } + }, + "required": ["sessionUpdate"], + "allOf": [ + { + "$ref": "#/$defs/PromptSuggestionsUpdate" + } + ] + }, + { + "description": "Announces a child session created and owned by this session, or updates\nthat ownership association's metadata.", "type": "object", "properties": { "sessionUpdate": { @@ -6228,7 +6415,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA message upsert observed in this session's transcript, sent to or\nreceived from another session.", + "description": "A message upsert observed in this session's transcript, sent to or\nreceived from another session.", "type": "object", "properties": { "sessionUpdate": { @@ -6244,7 +6431,7 @@ ] }, { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nOne content block appended to a sent or received session message.", + "description": "One content block appended to a sent or received session message.", "type": "object", "properties": { "sessionUpdate": { @@ -6472,6 +6659,26 @@ }, "required": ["sessionUpdate"] }, + { + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "replay_complete" + } + }, + "required": ["sessionUpdate"] + }, + { + "type": "object", + "properties": { + "sessionUpdate": { + "type": "string", + "const": "prompt_suggestions_update" + } + }, + "required": ["sessionUpdate"] + }, { "type": "object", "properties": { @@ -6891,22 +7098,6 @@ } ] }, - { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA URI pointing to a file containing the plan.", - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "file" - } - }, - "required": ["type"], - "allOf": [ - { - "$ref": "#/$defs/PlanFile" - } - ] - }, { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nRaw markdown content for the plan.", "type": "object", @@ -6954,16 +7145,6 @@ }, "required": ["type"] }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "const": "file" - } - }, - "required": ["type"] - }, { "type": "object", "properties": { @@ -7101,32 +7282,6 @@ }, "required": ["planId", "entries"] }, - "PlanFile": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA plan represented by a file URI.", - "type": "object", - "properties": { - "planId": { - "description": "The plan ID to update.", - "allOf": [ - { - "$ref": "#/$defs/PlanId" - } - ] - }, - "uri": { - "description": "The URI of the file containing the plan.", - "type": "string", - "format": "uri" - }, - "_meta": { - "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", - "type": ["object", "null"], - "x-deserialize-default-on-error": true, - "additionalProperties": true - } - }, - "required": ["planId", "uri"] - }, "PlanMarkdown": { "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA plan represented as raw markdown content.", "type": "object", @@ -7143,6 +7298,18 @@ "description": "Markdown content for the plan.", "type": "string" }, + "path": { + "description": "The absolute path of the file that holds this plan.\n\nOmitted and `null` are equivalent and mean the plan has no file. `content` is the\nplan to display whether or not `path` is set. Clients can use `path` to open the file.", + "anyOf": [ + { + "$ref": "#/$defs/AbsolutePath" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", "type": ["object", "null"], @@ -7480,8 +7647,62 @@ }, "required": ["compactionId", "content"] }, + "ReplayComplete": { + "description": "Marks the end of the history replay requested by `session/resume`.\n\nThe Agent sends this once on a session for each `session/resume` request\nwith a `replayFrom` cursor, after the last replayed update for that session\nand before the response. It sends it for an empty history too. Every update\nfor the session between the request and this update is replayed history.\nEvery update after it is not.\n\nThe Agent does not send it when `replayFrom` is omitted or `null`, or when\nit cannot finish the replay. A resume that ends without it delivered a\ntruncated replay.\n\nSee protocol docs: [Replay Complete](https://agentclientprotocol.com/protocol/v2/draft/session-setup#replay-complete)", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSuggestion": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA prompt the Agent predicts the user will send next.", + "type": "object", + "properties": { + "prompt": { + "description": "The content of the suggested prompt.\n\nRequired and non-null. Must contain at least one content block. This is\nthe same type as the `prompt` field of `session/prompt`, and the Client\ncan send it in that field unchanged.\n\nAgents MUST only use `text` and `resource_link` content blocks, and\nother content block types that both the Agent's `session.prompt`\ncapability and the Client's `session.promptSuggestions` capability\nadvertise. Agents MUST NOT send a prompt whose only content is empty\nor whitespace-only text.", + "type": "array", + "items": { + "$ref": "#/$defs/ContentBlock" + }, + "minItems": 1 + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["prompt"] + }, + "PromptSuggestionsUpdate": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nReplaces the session's current list of prompt suggestions.\n\nA prompt suggestion is a prompt the Agent predicts the user will send next.\nThe Client offers suggestions where the user writes the next prompt. The\nlist in the most recent update is the current list until the Agent sends\nanother update. A session starts with an empty list.\n\nSuggestions are not part of session history. Agents SHOULD NOT include\nthis update when they replay a session.\n\nAgents MUST only send this update when the Client advertised\nthe `session.promptSuggestions` Client capability.", + "type": "object", + "properties": { + "suggestions": { + "description": "The suggestions the Client can offer, ordered most likely first.\n\nRequired and non-null. An empty list withdraws the suggestions the\nClient is offering. A Client skips an entry it cannot parse and keeps\nthe rest.", + "type": "array", + "items": { + "$ref": "#/$defs/PromptSuggestion" + }, + "x-deserialize-skip-invalid-items": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + }, + "required": ["suggestions"] + }, "SubagentSessionCapabilities": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nClient-initiated session mutations permitted for a specific subagent session.\n\nA mutation requires an explicit per-child capability. Support for the method\non ordinary sessions does not grant support on a child.", + "description": "Client-initiated session mutations permitted for a specific subagent session.\n\nA mutation requires an explicit per-child capability. Support for the method\non ordinary sessions does not grant support on a child.", "type": "object", "properties": { "cancel": { @@ -7505,7 +7726,7 @@ } }, "SessionCancelCapabilities": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapability to cancel work in a subagent session without ending that session.\n\nSupplying `{}` advertises support. An omitted or `null` `cancel` does not.", + "description": "Capability to cancel work in a subagent session without ending that session.\n\nSupplying `{}` advertises support. An omitted or `null` `cancel` does not.", "type": "object", "properties": { "_meta": { @@ -7517,7 +7738,7 @@ } }, "SubagentUpdate": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nNotification that the enclosing parent session created and owns a child session.\n\nLater updates modify the existing association's metadata, not its ownership.\n\nSent on the immediate parent session. The first update for an unknown\n`sessionId` announces the child and MUST be sent\nbefore any live request or notification bearing the child's session ID,\nincluding messages naming it as sender or recipient. Child events are\ndelivered automatically. No separate child load, resume, or subscription is needed.\nUnderstanding this update, registering child sessions, and applying their\noperation restrictions are baseline v2 requirements. No Client capability\nis required.\n\nOnly `sessionId` is required. Other fields have\npatch semantics: omitted fields leave the stored value unchanged, `null`\nclears or unsets the value, and concrete values replace it. A concrete\n`_meta` object instead patches the stored metadata by top-level key. For\n`state`, `null` removes the current state report and leaves activity\nunconfirmed. It does not report idle or cancellation. A child whose capabilities are\nunset permits no Client-initiated session mutations.\n\n`cwd` and `additionalDirectories` report where the Agent placed the child.\nWhile unset, each inherits the immediate parent's effective value.\nThe title and description provide the parent's display metadata for the\nchild. Agents SHOULD mirror known child state changes in the parent's\n`state` using the same `StateUpdate` snapshot as the\nordinary `state_update` notification on the child session. This reports\nchild state, not a separate parent-owned lifecycle. Consumers should\ntreat duplicate reports idempotently.\nCompleting or cancelling work does not end the association: the parent may\nmessage the same child again. Individual messages and their outcomes do not\nchange this association.", + "description": "Notification that the enclosing parent session created and owns a child session.\n\nLater updates modify the existing association's metadata, not its ownership.\n\nSent on the immediate parent session. The first update for an unknown\n`sessionId` announces the child and MUST be sent\nbefore any live request or notification bearing the child's session ID,\nincluding messages naming it as sender or recipient. Child events are\ndelivered automatically. No separate child load, resume, or subscription is needed.\nUnderstanding this update, registering child sessions, and applying their\noperation restrictions are baseline v2 requirements. No Client capability\nis required.\n\nOnly `sessionId` is required. Other fields have\npatch semantics: omitted fields leave the stored value unchanged, `null`\nclears or unsets the value, and concrete values replace it. A concrete\n`_meta` object instead patches the stored metadata by top-level key. For\n`state`, `null` removes the current state report and leaves activity\nunconfirmed. It does not report idle or cancellation. A child whose capabilities are\nunset permits no Client-initiated session mutations.\n\n`cwd` and `additionalDirectories` report where the Agent placed the child.\nWhile unset, each inherits the immediate parent's effective value.\nThe title and description provide the parent's display metadata for the\nchild. Agents SHOULD mirror known child state changes in the parent's\n`state` using the same `StateUpdate` snapshot as the\nordinary `state_update` notification on the child session. This reports\nchild state, not a separate parent-owned lifecycle. Consumers should\ntreat duplicate reports idempotently.\nCompleting or cancelling work does not end the association: the parent may\nmessage the same child again. Individual messages and their outcomes do not\nchange this association.", "type": "object", "properties": { "sessionId": { @@ -7590,7 +7811,7 @@ "required": ["sessionId"] }, "SessionMessage": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nAn upsert for an inter-session message.", + "description": "An upsert for an inter-session message.", "type": "object", "properties": { "messageId": { @@ -7668,7 +7889,7 @@ "required": ["messageId"] }, "SessionMessageChunk": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nA streamed content block of an inter-session message.\n\nA chunk carries only content. The message's participants and references come from\n`session_message` upserts, which can arrive before, between, or after its chunks.", + "description": "A streamed content block of an inter-session message.\n\nA chunk carries only content. The message's participants and references come from\n`session_message` upserts, which can arrive before, between, or after its chunks.", "type": "object", "properties": { "messageId": { @@ -8063,6 +8284,18 @@ ], "x-deserialize-default-on-error": true }, + "session": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nSession-related capabilities supported by the client.\n\nOptional. Omitted and `null` are equivalent and mean the client does not advertise any\nsession-related capabilities. This object only groups capabilities. Supplying `{}`\nadvertises nothing.", + "anyOf": [ + { + "$ref": "#/$defs/ClientSessionCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", "type": ["object", "null"], @@ -8167,6 +8400,162 @@ } } }, + "ClientSessionCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nSession-related capabilities supported by the client.\n\nThis object only groups capabilities. Unlike the agent's `session`\ncapability, it is not a support marker, and supplying `{}` advertises\nnothing.", + "type": "object", + "properties": { + "promptSuggestions": { + "description": "Support for `prompt_suggestions_update` session updates.\n\nOptional. Omitted and `null` are equivalent and mean the client does not advertise support.\nSupplying `{}` means the client can offer prompt suggestions made of\n`text` and `resource_link` content blocks.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSuggestionsCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "commands": { + "description": "Command types the client understands in addition to `command`.\n\nOptional. Omitted and `null` are equivalent and mean the client does not advertise support\nfor any of them. This object only groups capabilities. Supplying `{}` advertises nothing.", + "anyOf": [ + { + "$ref": "#/$defs/CommandsCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSuggestionsCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nClient support for `prompt_suggestions_update` session updates.\n\nSupplying `{}` means the client can offer prompt suggestions made of `text`\nand `resource_link` content blocks. The client opts in to other content\nblock types with the fields below. They have the same names as the agent's\n`session.prompt` capability.", + "type": "object", + "properties": { + "image": { + "description": "Whether prompt suggestions may include `image` content blocks.\n\nOptional. Omitted and `null` are equivalent and mean the client does not advertise support.\nSupplying `{}` means the client can offer suggestions that include images.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSuggestionsImageCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "audio": { + "description": "Whether prompt suggestions may include `audio` content blocks.\n\nOptional. Omitted and `null` are equivalent and mean the client does not advertise support.\nSupplying `{}` means the client can offer suggestions that include audio.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSuggestionsAudioCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "embeddedContext": { + "description": "Whether prompt suggestions may include `resource` content blocks.\n\nOptional. Omitted and `null` are equivalent and mean the client does not advertise support.\nSupplying `{}` means the client can offer suggestions that include\nembedded resources.", + "anyOf": [ + { + "$ref": "#/$defs/PromptSuggestionsEmbeddedContextCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSuggestionsImageCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapabilities for image content in prompt suggestions.\n\nSupplying `{}` means the client can offer suggestions that include images.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSuggestionsAudioCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapabilities for audio content in prompt suggestions.\n\nSupplying `{}` means the client can offer suggestions that include audio.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "PromptSuggestionsEmbeddedContextCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCapabilities for embedded resources in prompt suggestions.\n\nSupplying `{}` means the client can offer suggestions that include\n`resource` content blocks.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "CommandsCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nCommand types the client understands in addition to `command`.\n\nAgents MUST NOT send a command type listed here unless the client\nadvertised it. This object only groups capabilities. Supplying `{}`\nadvertises nothing.", + "type": "object", + "properties": { + "skill": { + "description": "Support for `skill` command entries.\n\nOptional. Omitted and `null` are equivalent and mean the client does not advertise support.\nSupplying `{}` means the agent may send `skill` entries in `availableCommands`. The\nclient invokes them with `skill` content blocks.", + "anyOf": [ + { + "$ref": "#/$defs/CommandsSkillCapabilities" + }, + { + "type": "null" + } + ], + "x-deserialize-default-on-error": true + }, + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, + "CommandsSkillCapabilities": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nClient support for `skill` command entries.\n\nSupplying `{}` means the agent may send `skill` entries in\n`availableCommands`. The client invokes them with `skill` content blocks.", + "type": "object", + "properties": { + "_meta": { + "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", + "type": ["object", "null"], + "x-deserialize-default-on-error": true, + "additionalProperties": true + } + } + }, "LoginAuthRequest": { "description": "Request parameters for the `auth/login` method.\n\nSpecifies which authentication method to use.\n\nAgents MUST support this method when their `initialize` response included\na non-empty `authMethods` list. Clients MUST NOT call this method\nwhen `authMethods` was omitted or empty.", "type": "object", @@ -8338,7 +8727,7 @@ ] }, "configOptions": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial values for the session's config options.\n\nOptional. Omitted and `null` are equivalent to an empty list. Clients MUST only send\nthis when the Agent advertises `session.options`.\n\nThe Agent applies the entries in list order, as if the Client had called\n`session/set_config_option` for each entry after the session was set up. Malformed\nentries and duplicate `configId` values fail the request with `-32602`. The Agent MUST\nignore an entry whose `configId` is unknown, whose `type` does not match the option,\nor whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually\nhas. The Agent SHOULD NOT send `config_option_update` notifications for these entries.", + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial values for the session's config options.\n\nOptional. Omitted and `null` are equivalent to an empty list. Clients MUST only send\nthis when the Agent advertises `session.options`.\n\nThe Agent applies the entries in list order, as if the Client had called\n`session/set_config_option` for each entry after the session was set up. Malformed\nentries and duplicate `configId` values fail the request with `-32602`. The Agent MUST\nignore an entry whose `configId` is unknown, whose `type` does not match the option,\nor whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually\nhas. The Agent SHOULD NOT send `config_option_update` notifications for these entries,\nand SHOULD NOT send other `session/update` notifications caused by applying them\nbefore it responds.", "type": "array", "items": { "$ref": "#/$defs/InitialSessionConfigOption" @@ -8860,6 +9249,14 @@ } ] }, + "configOptions": { + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial values for the forked session's config options.\n\nOptional. Omitted and `null` are equivalent to an empty list. Clients MUST only send\nthis when the Agent advertises `session.options`.\n\nThe Agent applies the entries in list order on top of the configuration the fork\nstarts with, as if the Client had called `session/set_config_option` for each entry\nafter the session was set up. Malformed entries and duplicate `configId` values fail\nthe request with `-32602`. The Agent MUST ignore an entry whose `configId` is unknown,\nwhose `type` does not match the option, or whose value is not allowed when it is\napplied. The `configOptions` in the response contain the values the session actually\nhas. The Agent SHOULD NOT send `config_option_update` notifications for these entries,\nand SHOULD NOT send other `session/update` notifications caused by applying them\nbefore it responds.", + "type": "array", + "items": { + "$ref": "#/$defs/InitialSessionConfigOption" + }, + "x-deserialize-default-on-null": true + }, "_meta": { "description": "The _meta property is reserved by ACP to allow clients and agents to attach additional\nmetadata to their interactions. Omitted and `null` are equivalent and mean no metadata.\nImplementations MUST NOT assign meaning to keys they do not recognize.\n\nSee protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility)", "type": ["object", "null"], @@ -8941,7 +9338,7 @@ "x-method": "session/status" }, "SessionOptionsRequest": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nRequest parameters for `session/options`, which returns the config options a session\nwould start with, without creating or resuming a session.\n\nOnly available if the Agent supports the `session.options` capability.\n\n`session/options` MUST NOT create or resume a session, send `session/update`\nnotifications, add an entry to `session/list`, or change an existing session. Agents\nSHOULD NOT connect to MCP servers to answer it.", + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nRequest parameters for `session/options`, which returns the config options a session\nwould start with, without creating or resuming a session.\n\nOnly available if the Agent supports the `session.options` capability.\n\n`session/options` MUST NOT create or resume a session, send `session/update`\nnotifications, add an entry to `session/list`, or change an existing session. Agents\nSHOULD NOT connect to MCP servers to answer it. Agents MAY start their runtime to resolve\nthe options. The rules above still hold for it.", "type": "object", "properties": { "cwd": { @@ -9068,7 +9465,7 @@ ] }, "configOptions": { - "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial values for the session's config options.\n\nOptional. Omitted and `null` are equivalent to an empty list. Clients MUST only send\nthis when the Agent advertises `session.options`.\n\nThe Agent applies the entries in list order, as if the Client had called\n`session/set_config_option` for each entry after the session was set up. Malformed\nentries and duplicate `configId` values fail the request with `-32602`. The Agent MUST\nignore an entry whose `configId` is unknown, whose `type` does not match the option,\nor whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually\nhas. The Agent SHOULD NOT send `config_option_update` notifications for these entries.\n\nThe Agent applies the entries before it replays history for `replayFrom` and before\nit responds.", + "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nInitial values for the session's config options.\n\nOptional. Omitted and `null` are equivalent to an empty list. Clients MUST only send\nthis when the Agent advertises `session.options`.\n\nThe Agent applies the entries in list order, as if the Client had called\n`session/set_config_option` for each entry after the session was set up. Malformed\nentries and duplicate `configId` values fail the request with `-32602`. The Agent MUST\nignore an entry whose `configId` is unknown, whose `type` does not match the option,\nor whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually\nhas. The Agent SHOULD NOT send `config_option_update` notifications for these entries\non the requesting connection. If the session is active on other connections, the\nAgent SHOULD send `config_option_update` on those connections.\n\nThe Agent applies the entries before it replays history for `replayFrom` and before\nit responds.", "type": "array", "items": { "$ref": "#/$defs/InitialSessionConfigOption" diff --git a/scripts/generate.js b/scripts/generate.js index 1ef42c4a..53e2eb83 100644 --- a/scripts/generate.js +++ b/scripts/generate.js @@ -12,8 +12,8 @@ import * as prettier from "prettier"; import ts from "typescript"; import { generateBindings } from "./generate-bindings.js"; -const CURRENT_V1_SCHEMA_RELEASE = "schema-v1.31.0"; -const CURRENT_V2_SCHEMA_RELEASE = "schema-v2.0.0-alpha.15"; +const CURRENT_V1_SCHEMA_RELEASE = "schema-v1.32.0"; +const CURRENT_V2_SCHEMA_RELEASE = "schema-v2.0.0-alpha.16"; const CHECK_GENERATED = process.argv.includes("--check"); // ── Extensible-union pipeline ──────────────────────────────────────────────── diff --git a/src/examples/README.md b/src/examples/README.md index 24de7df0..c778caa0 100644 --- a/src/examples/README.md +++ b/src/examples/README.md @@ -4,7 +4,7 @@ This directory contains examples using the [ACP](https://agentclientprotocol.com - [`agent.ts`](./agent.ts) - A minimal agent implementation that simulates LLM interaction - [`client.ts`](./client.ts) - A minimal client implementation that spawns the [`agent.ts`](./agent.ts) as a subprocess -- [`dual-version-agent.ts`](./dual-version-agent.ts) - An agent that serves ACP v1 and the experimental draft ACP v2 through `agentProtocolRouter`, holding prompts that arrive during a turn, cancelling a pending v2 permission request with `$/cancel_request` on `session/cancel` or `session/close`, and making `session/close` idempotent +- [`dual-version-agent.ts`](./dual-version-agent.ts) - An agent that serves ACP v1 and the experimental draft ACP v2 through `agentProtocolRouter`, holding prompts that arrive during a turn, ending a v2 history replay with `replay_complete`, cancelling a pending v2 permission request with `$/cancel_request` on `session/cancel` or `session/close`, and making `session/close` idempotent - [`http-server.ts`](./http-server.ts) - A minimal ACP Streamable HTTP server with WebSocket upgrade support - [`http-client.ts`](./http-client.ts) - A minimal client using `createHttpStream` - [`ws-client.ts`](./ws-client.ts) - A minimal client using `createWebSocketStream` diff --git a/src/examples/dual-version-agent.ts b/src/examples/dual-version-agent.ts index 435cec59..75b32640 100644 --- a/src/examples/dual-version-agent.ts +++ b/src/examples/dual-version-agent.ts @@ -59,6 +59,8 @@ type V2Session = { state: v2.StateUpdate; turn?: V2Turn; held: HeldPrompt[]; + /** Settles when the history replay in progress has sent `replay_complete`. */ + replay?: Promise; }; // No `session.mcp` transports are advertised, so `assertSupportedMcpServers` @@ -122,21 +124,35 @@ const v2Agent = v2 } if (params.replayFrom?.type === "start") { - for (const update of session.history) { - await client.notify(v2.methods.client.session.update, { - sessionId: params.sessionId, - update, - }); + // The history up to here is replayed. A turn that is still running sends + // its later updates through `sendV2Update`, which holds them until + // `replay_complete` is sent. + const replay = replayV2History(params.sessionId, client, [ + ...session.history, + ]); + const settled = replay.then( + () => {}, + () => {}, + ); + session.replay = settled; + try { + await replay; + } finally { + if (session.replay === settled) { + session.replay = undefined; + } } } else if (params.replayFrom) { + // An unsupported cursor replays nothing and gets no `replay_complete`. throw v2.RequestError.invalidParams( `Unsupported replay cursor ${params.replayFrom.type}`, ); } session.active = true; - // Synchronize current state after replay, even when no replay was requested. - // History contains messages only; historical states are not live activity. + // Synchronize current state after `replay_complete`, or right away when no + // replay was requested. History contains messages only; historical states + // are not live activity. await client.notify(v2.methods.client.session.update, { sessionId: params.sessionId, update: { sessionUpdate: "session_info_update" }, @@ -284,6 +300,42 @@ function insertV2Prompt( return { messageId: userMessageId }; } +/** + * Replays `history`, then marks the end of the replay with `replay_complete`. + * The marker is sent exactly once per replay, also when the history is empty. + */ +async function replayV2History( + sessionId: string, + client: v2.AgentContext, + history: v2.SessionUpdate[], +): Promise { + for (const update of history) { + await client.notify(v2.methods.client.session.update, { + sessionId, + update, + }); + } + await client.notify(v2.methods.client.session.update, { + sessionId, + update: { sessionUpdate: "replay_complete" }, + }); +} + +/** + * Sends a live update. Between a `session/resume` request with `replayFrom` + * and its `replay_complete`, the Client reads every update for the session + * as replayed history, so live updates wait for the replay to finish. + */ +async function sendV2Update( + sessionId: string, + client: v2.AgentContext, + session: V2Session, + update: v2.SessionUpdate, +): Promise { + await session.replay; + await client.notify(v2.methods.client.session.update, { sessionId, update }); +} + function requireV2Session(sessionId: string): V2Session { const session = v2Sessions.get(sessionId); if (!session) { @@ -354,10 +406,13 @@ async function runV2ToolCall( let approved = false; try { session.state = { state: "requires_action" }; - await client.notify(v2.methods.client.session.update, { - sessionId, - update: { sessionUpdate: "state_update", ...session.state }, + await sendV2Update(sessionId, client, session, { + sessionUpdate: "state_update", + ...session.state, }); + // A permission request is not history either, so it also waits for a + // replay in progress. + await session.replay; // `stopWaitingOnCancel` makes an abort send `$/cancel_request` and reject // with `-32800` right away. The Client still answers, normally with the // `cancelled` outcome, and the SDK drops that late answer. @@ -387,20 +442,17 @@ async function runV2ToolCall( response.outcome.optionId === "allow"; session.state = { state: "running" }; - await client.notify(v2.methods.client.session.update, { - sessionId, - update: { sessionUpdate: "state_update", ...session.state }, + await sendV2Update(sessionId, client, session, { + sessionUpdate: "state_update", + ...session.state, }); } finally { // A rejected, cancelled, or abandoned permission request leaves the tool // call unapproved, so it ends as `cancelled` rather than `pending`. - await client.notify(v2.methods.client.session.update, { - sessionId, - update: { - sessionUpdate: "tool_call_update", - toolCallId, - status: approved ? "completed" : "cancelled", - }, + await sendV2Update(sessionId, client, session, { + sessionUpdate: "tool_call_update", + toolCallId, + status: approved ? "completed" : "cancelled", }); } } @@ -413,16 +465,13 @@ async function runV2Turn( userMessage: v2.SessionUpdate, ): Promise { try { - await client.notify(v2.methods.client.session.update, { - sessionId: params.sessionId, - update: userMessage, - }); + await sendV2Update(params.sessionId, client, session, userMessage); signal.throwIfAborted(); session.state = { state: "running" }; - await client.notify(v2.methods.client.session.update, { - sessionId: params.sessionId, - update: { sessionUpdate: "state_update", ...session.state }, + await sendV2Update(params.sessionId, client, session, { + sessionUpdate: "state_update", + ...session.state, }); await waitForWork(signal); @@ -436,16 +485,13 @@ async function runV2Turn( content: [{ type: "text", text: "Hello from the v2 implementation." }], }; session.history.push(agentMessage); - await client.notify(v2.methods.client.session.update, { - sessionId: params.sessionId, - update: agentMessage, - }); + await sendV2Update(params.sessionId, client, session, agentMessage); signal.throwIfAborted(); session.state = { state: "idle", stopReason: "end_turn" }; - await client.notify(v2.methods.client.session.update, { - sessionId: params.sessionId, - update: { sessionUpdate: "state_update", ...session.state }, + await sendV2Update(params.sessionId, client, session, { + sessionUpdate: "state_update", + ...session.state, }); } catch (error) { // Insertion already succeeded. Report failures on idle, not on the prompt RPC. @@ -463,9 +509,9 @@ async function runV2Turn( }; session.state = { state: "idle", ...completion }; - await client.notify(v2.methods.client.session.update, { - sessionId: params.sessionId, - update: { sessionUpdate: "state_update", ...session.state }, + await sendV2Update(params.sessionId, client, session, { + sessionUpdate: "state_update", + ...session.state, }); } } diff --git a/src/v1/acp.test.ts b/src/v1/acp.test.ts index 3d1cc1b0..03b4ae8f 100644 --- a/src/v1/acp.test.ts +++ b/src/v1/acp.test.ts @@ -4803,6 +4803,263 @@ describe("Connection", () => { }); }); + it("round-trips unstable prompt suggestions and fork config options", async () => { + const updates: SessionUpdate[] = []; + let offersSuggestions = false; + let receivedFork: ForkSessionRequest | undefined; + const suggestions = [ + { + prompt: [ + { + type: "text" as const, + text: "Run the test suite and fix what breaks", + }, + ], + }, + { + prompt: [ + { type: "text" as const, text: "Add tests for " }, + { + type: "resource_link" as const, + name: "parser.rs", + uri: "file:///home/user/project/src/parser.rs", + }, + ], + }, + ]; + createAgent() + .onRequest(AGENT_METHODS.initialize, ({ params }) => { + offersSuggestions = + params.clientCapabilities?.session?.promptSuggestions != null; + return { + protocolVersion: PROTOCOL_VERSION, + agentCapabilities: { sessionCapabilities: { fork: {}, options: {} } }, + }; + }) + .onRequest(AGENT_METHODS.session_fork, ({ params }) => { + receivedFork = params; + return { sessionId: "forked" }; + }) + .onRequest(AGENT_METHODS.session_prompt, async ({ params, client }) => { + if (offersSuggestions) { + // Accepting a prompt makes the earlier list stale, so withdraw it. + await client.notify(CLIENT_METHODS.session_update, { + sessionId: params.sessionId, + update: { + sessionUpdate: "prompt_suggestions_update", + suggestions: [], + }, + }); + await client.notify(CLIENT_METHODS.session_update, { + sessionId: params.sessionId, + update: { + sessionUpdate: "prompt_suggestions_update", + suggestions: [ + suggestions[0], + // The client skips a suggestion it cannot parse. + { prompt: [{ type: "hologram", data: "AAAA" }] } as never, + suggestions[1], + ], + }, + }); + } + return { stopReason: "end_turn" }; + }) + .connect(ndJsonStream(agentToClient.writable, clientToAgent.readable)); + const agent = createClient() + .onNotification(CLIENT_METHODS.session_update, ({ params }) => { + updates.push(params.update); + }) + .connect( + ndJsonStream(clientToAgent.writable, agentToClient.readable), + ).agent; + + await agent.request(AGENT_METHODS.initialize, { + protocolVersion: PROTOCOL_VERSION, + clientCapabilities: { session: { promptSuggestions: {} } }, + }); + await agent.request(AGENT_METHODS.session_prompt, { + sessionId: "s1", + prompt: [{ type: "text", text: "hi" }], + }); + await vi.waitFor(() => expect(updates).toHaveLength(2)); + expect(updates).toEqual([ + { sessionUpdate: "prompt_suggestions_update", suggestions: [] }, + { sessionUpdate: "prompt_suggestions_update", suggestions }, + ]); + // A suggestion has the type of the `prompt` field of `session/prompt`. + const [update] = updates.slice(1); + if (update.sessionUpdate === "prompt_suggestions_update") { + const accepted: PromptRequest["prompt"] = update.suggestions[0].prompt; + expect(accepted).toEqual(suggestions[0].prompt); + } + + await agent.request(AGENT_METHODS.session_fork, { + sessionId: "s1", + cwd: "/work", + configOptions: [ + { configId: "model", value: "model-2" }, + { configId: "fast", type: "boolean", value: true }, + ], + }); + expect(receivedFork).toEqual({ + sessionId: "s1", + cwd: "/work", + configOptions: [ + { configId: "model", value: "model-2" }, + { configId: "fast", type: "boolean", value: true }, + ], + }); + // A malformed entry fails the request instead of being dropped. + await expect( + agent.request(AGENT_METHODS.session_fork, { + sessionId: "s1", + cwd: "/work", + configOptions: [{ type: "boolean", value: true }], + } as never), + ).rejects.toMatchObject({ code: -32602 }); + }); + + it("validates unstable prompt suggestions, plan paths, the speed category and fork config options", () => { + const parse = (update: unknown) => unstableZod.zSessionUpdate.parse(update); + const text = (text: string) => ({ prompt: [{ type: "text", text }] }); + + // One bad block fails the whole suggestion. The rest of the list is kept. + expect( + parse({ + sessionUpdate: "prompt_suggestions_update", + suggestions: [ + text("Keep the first"), + { text: "A suggestion without a prompt" }, + { prompt: "Not a list" }, + { prompt: [{ type: "text", text: "Open " }, { type: "text" }] }, + { prompt: [{ type: "hologram", data: "AAAA" }] }, + { prompt: [] }, + "not an object", + { ...text("Keep the last"), _meta: false }, + ], + }), + ).toEqual({ + sessionUpdate: "prompt_suggestions_update", + suggestions: [text("Keep the first"), text("Keep the last")], + }); + // An empty list withdraws the suggestions. + expect( + parse({ sessionUpdate: "prompt_suggestions_update", suggestions: [] }), + ).toEqual({ sessionUpdate: "prompt_suggestions_update", suggestions: [] }); + // The list is required and non-null. Only its entries are lenient. + for (const malformed of [ + { suggestions: null }, + { suggestions: "none" }, + { suggestions: { prompt: [] } }, + {}, + ]) { + expect(() => + parse({ sessionUpdate: "prompt_suggestions_update", ...malformed }), + ).toThrow(); + } + expect( + stableZod.zSessionUpdate.safeParse({ + sessionUpdate: "prompt_suggestions_update", + suggestions: [], + }).success, + ).toBe(false); + + // Only an object advertises the capability or one of its content types. + const capability = (clientCapabilities: unknown) => + unstableZod.zClientCapabilities.parse(clientCapabilities).session + ?.promptSuggestions; + expect(capability({ session: { promptSuggestions: {} } })).toEqual({}); + for (const unsupported of [ + {}, + { session: null }, + { session: {} }, + { session: { promptSuggestions: null } }, + { session: { promptSuggestions: false } }, + { session: { promptSuggestions: true } }, + { session: { promptSuggestions: "supported" } }, + ]) { + expect(capability(unsupported) ?? undefined).toBeUndefined(); + } + expect( + capability({ + session: { + promptSuggestions: { image: true, audio: {}, embeddedContext: {} }, + }, + }), + ).toEqual({ audio: {}, embeddedContext: {} }); + expect( + stableZod.zClientCapabilities.parse({ + session: { promptSuggestions: {} }, + }).session, + ).not.toHaveProperty("promptSuggestions"); + + // A markdown plan can name the file that holds it. `content` is still + // the plan to display. + const markdown = { + type: "markdown", + planId: "plan-1", + content: "# Next steps", + }; + expect( + unstableZod.zPlanUpdateContent.parse({ + ...markdown, + path: "/home/user/project/PLAN.md", + }), + ).toEqual({ ...markdown, path: "/home/user/project/PLAN.md" }); + // A malformed path means the plan has no file. + expect( + unstableZod.zPlanUpdateContent.parse({ ...markdown, path: 42 }), + ).toEqual(markdown); + expect( + unstableZod.zPlanUpdateContent.parse({ ...markdown, path: null }), + ).toEqual({ ...markdown, path: null }); + // The `file` plan type is gone. + expect(unstableZod).not.toHaveProperty("zPlanFile"); + expect( + unstableZod.zPlanUpdateContent.safeParse({ + type: "file", + planId: "plan-1", + uri: "file:///tmp/plan.md", + }).success, + ).toBe(false); + + expect( + unstableZod.zSessionConfigOption.parse({ + type: "boolean", + id: "fast", + name: "Fast mode", + category: "speed", + currentValue: true, + }), + ).toMatchObject({ category: "speed", currentValue: true }); + + // `null` means an empty list, and entries stay strict. + expect( + unstableZod.zForkSessionRequest.parse({ + sessionId: "s1", + cwd: "/work", + configOptions: null, + }), + ).toEqual({ sessionId: "s1", cwd: "/work", configOptions: [] }); + expect( + unstableZod.zForkSessionRequest.parse({ sessionId: "s1", cwd: "/work" }), + ).not.toHaveProperty("configOptions"); + for (const entry of [ + { type: "boolean", value: true }, + { configId: "fast", type: "boolean" }, + "model-2", + ]) { + expect(() => + unstableZod.zForkSessionRequest.parse({ + sessionId: "s1", + cwd: "/work", + configOptions: [entry], + }), + ).toThrow(); + } + }); + it("handles NES notifications", async () => { const notificationLog: unknown[] = []; diff --git a/src/v1/acp.ts b/src/v1/acp.ts index 318fb261..3e66f30f 100644 --- a/src/v1/acp.ts +++ b/src/v1/acp.ts @@ -1513,10 +1513,13 @@ export type AgentRequestHandlersByMethod = { /** * Handles `session/options`. Respond with the modes and config options a * session would start with. With `sessionId`, respond with what - * `session/load` or `session/resume` would return for that session. It - * must not create, load, resume, or change a session, send - * `session/update` notifications, or add an entry to `session/list`. An - * unknown session gets `-32002`. + * `session/load` or `session/resume` would return for that session. + * Clients send `sessionId` only when the agent supports `session/load`, + * `session/resume`, or `session/fork`. Before a fork, it is the source + * session's ID. It must not create, load, resume, or change a session, + * send `session/update` notifications, or add an entry to `session/list`. + * The agent may start its runtime to resolve the options, and these rules + * still hold for it. An unknown session gets `-32002`. */ [schema.AGENT_METHODS.session_options]: AgentRequestHandler< schema.SessionOptionsRequest, diff --git a/src/v1/unstable/schema/index.ts b/src/v1/unstable/schema/index.ts index 29c9294d..5ea1eb0c 100644 --- a/src/v1/unstable/schema/index.ts +++ b/src/v1/unstable/schema/index.ts @@ -136,7 +136,6 @@ export type { PlanEntry, PlanEntryPriority, PlanEntryStatus, - PlanFile, PlanId, PlanItems, PlanMarkdown, @@ -146,6 +145,12 @@ export type { PromptCapabilities, PromptRequest, PromptResponse, + PromptSuggestion, + PromptSuggestionsAudioCapabilities, + PromptSuggestionsCapabilities, + PromptSuggestionsEmbeddedContextCapabilities, + PromptSuggestionsImageCapabilities, + PromptSuggestionsUpdate, ProtocolVersion, ProviderCurrentConfig, ProviderId, diff --git a/src/v1/unstable/schema/types.gen.ts b/src/v1/unstable/schema/types.gen.ts index 174d9a2f..407ceea5 100644 --- a/src/v1/unstable/schema/types.gen.ts +++ b/src/v1/unstable/schema/types.gen.ts @@ -2881,7 +2881,7 @@ export type SessionConfigId = string; * Category names that do not begin with `_` are reserved for the ACP spec. */ export type SessionConfigOptionCategory = - "mode" | "model" | "model_config" | "thought_level" | string; + "mode" | "model" | "model_config" | "speed" | "thought_level" | string; /** * Unique identifier for a session configuration option value. @@ -3611,6 +3611,9 @@ export type SessionUpdate = | (CompactionSummaryChunk & { sessionUpdate: "compaction_summary_chunk"; }) + | (PromptSuggestionsUpdate & { + sessionUpdate: "prompt_suggestions_update"; + }) | (SubagentUpdate & { sessionUpdate: "subagent_update"; }) @@ -3807,9 +3810,6 @@ export type PlanUpdateContent = | (PlanItems & { type: "items"; }) - | (PlanFile & { - type: "file"; - }) | (PlanMarkdown & { type: "markdown"; }); @@ -3858,36 +3858,6 @@ export type PlanItems = { } | null; }; -/** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A plan represented by a file URI. - * - * @experimental - */ -export type PlanFile = { - /** - * The plan ID to update. - */ - planId: PlanId; - /** - * The URI of the file containing the plan. - */ - uri: string; - /** - * The _meta property is reserved by ACP to allow clients and agents to attach additional - * metadata to their interactions. Implementations MUST NOT make assumptions about values at - * these keys. - * - * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility) - */ - _meta?: { - [key: string]: unknown; - } | null; -}; - /** * **UNSTABLE** * @@ -3906,6 +3876,14 @@ export type PlanMarkdown = { * Markdown content for the plan. */ content: string; + /** + * The absolute path of the file that holds this plan. + * + * This field is optional. Omitting it or sending `null` both mean that the plan + * has no file. `content` is the plan to display whether or not `path` is set. + * Clients can use `path` to open the file. + */ + path?: string | null; /** * The _meta property is reserved by ACP to allow clients and agents to attach additional * metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -4277,6 +4255,83 @@ export type CompactionSummaryChunk = { } | null; }; +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * A prompt the Agent predicts the user will send next. + * + * @experimental + */ +export type PromptSuggestion = { + /** + * The content of the suggested prompt. + * + * Required and non-null. Must contain at least one content block. This is + * the same type as the `prompt` field of `session/prompt`, and the Client + * can send it in that field unchanged. + * + * Agents MUST only use `text` and `resource_link` content blocks, and + * other content block types that both the Agent's prompt capabilities + * and the Client's `session.promptSuggestions` capability advertise. + * Agents MUST NOT send a prompt whose only content is empty or + * whitespace-only text. + */ + prompt: Array; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Replaces the session's current list of prompt suggestions. + * + * A prompt suggestion is a prompt the Agent predicts the user will send next. + * The Client offers suggestions where the user writes the next prompt. The + * list in the most recent update is the current list until the Agent sends + * another update. A session starts with an empty list. + * + * Suggestions are not part of session history. Agents SHOULD NOT include + * this update when they replay a session. + * + * Agents MUST only send this update when the Client advertised + * the `session.promptSuggestions` Client capability. + * + * @experimental + */ +export type PromptSuggestionsUpdate = { + /** + * The suggestions the Client can offer, ordered most likely first. + * + * Required and non-null. An empty list withdraws the suggestions the + * Client is offering. A Client skips an entry it cannot parse and keeps + * the rest. + */ + suggestions: Array; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * **UNSTABLE** * @@ -4485,7 +4540,10 @@ export type RequiresActionStateUpdate = { * snapshot normally. * * This is not a task outcome or session closure. It does not cancel work, resolve - * pending requests, or revoke capabilities. Capabilities are updated separately. + * pending requests, or revoke capabilities. If a control is no longer available, + * the Agent updates the capabilities explicitly. Unfinished tool calls and + * terminals stay as last reported. The Agent does not invent a final status + * for them, and Clients do not present them as confirmed live activity. * * @experimental */ @@ -4963,6 +5021,20 @@ export type ClientSessionCapabilities = { * Supplying `{}` means the client can present notices to the user. */ notices?: NoticeCapabilities | null; + /** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Support for `prompt_suggestions_update` session updates. + * + * Optional. Omitted or `null` both mean the client does not advertise support. + * Supplying `{}` means the client can offer prompt suggestions made of + * `text` and `resource_link` content blocks. + * + * @experimental + */ + promptSuggestions?: PromptSuggestionsCapabilities | null; /** * The _meta property is reserved by ACP to allow clients and agents to attach additional * metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -5032,6 +5104,128 @@ export type NoticeCapabilities = { [key: string]: unknown; }; +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Client support for `prompt_suggestions_update` session updates. + * + * Supplying `{}` means the client can offer prompt suggestions made of `text` + * and `resource_link` content blocks. The client opts in to other content + * block types with the fields below. They have the same names as the agent's + * prompt capabilities. + * + * @experimental + */ +export type PromptSuggestionsCapabilities = { + /** + * Whether prompt suggestions may include `image` content blocks. + * + * Optional. Omitted or `null` both mean the client does not advertise support. + * Supplying `{}` means the client can offer suggestions that include images. + */ + image?: PromptSuggestionsImageCapabilities | null; + /** + * Whether prompt suggestions may include `audio` content blocks. + * + * Optional. Omitted or `null` both mean the client does not advertise support. + * Supplying `{}` means the client can offer suggestions that include audio. + */ + audio?: PromptSuggestionsAudioCapabilities | null; + /** + * Whether prompt suggestions may include `resource` content blocks. + * + * Optional. Omitted or `null` both mean the client does not advertise support. + * Supplying `{}` means the client can offer suggestions that include + * embedded resources. + */ + embeddedContext?: PromptSuggestionsEmbeddedContextCapabilities | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for image content in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include images. + * + * @experimental + */ +export type PromptSuggestionsImageCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for audio content in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include audio. + * + * @experimental + */ +export type PromptSuggestionsAudioCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for embedded resources in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include + * `resource` content blocks. + * + * @experimental + */ +export type PromptSuggestionsEmbeddedContextCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v1/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * **UNSTABLE** * @@ -5383,7 +5577,9 @@ export type NewSessionRequest = { * entries and duplicate `configId` values fail the request with `-32602`. The Agent MUST * ignore an entry whose `configId` is unknown, whose `type` does not match the option, * or whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually - * has. The Agent SHOULD NOT send `config_option_update` notifications for these entries. + * has. The Agent SHOULD NOT send `config_option_update` notifications for these entries, + * and SHOULD NOT send other `session/update` notifications caused by applying them + * before it responds. * * @experimental */ @@ -5856,6 +6052,29 @@ export type ForkSessionRequest = { * for the source session. */ systemPrompt?: SystemPrompt | null; + /** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Initial values for the forked session's config options. + * + * Optional. Omitted and `null` are equivalent to an empty list. Clients MUST only send + * this when the Agent advertises `sessionCapabilities.options`. + * + * The Agent applies the entries in list order on top of the configuration the fork + * starts with, as if the Client had called `session/set_config_option` for each entry + * after the session was set up. Malformed entries and duplicate `configId` values fail + * the request with `-32602`. The Agent MUST ignore an entry whose `configId` is unknown, + * whose `type` does not match the option, or whose value is not allowed when it is + * applied. The `configOptions` in the response contain the values the session actually + * has. The Agent SHOULD NOT send `config_option_update` notifications for these entries, + * and SHOULD NOT send other `session/update` notifications caused by applying them + * before it responds. + * + * @experimental + */ + configOptions?: Array; /** * The _meta property is reserved by ACP to allow clients and agents to attach additional * metadata to their interactions. Implementations MUST NOT make assumptions about values at @@ -5979,7 +6198,8 @@ export type SessionStatusRequest = { * * `session/options` MUST NOT create or resume a session, send `session/update` * notifications, add an entry to `session/list`, or change an existing session. Agents - * SHOULD NOT connect to MCP servers to answer it. + * SHOULD NOT connect to MCP servers to answer it. Agents MAY start their runtime to resolve + * the options. The rules above still hold for it. * * @experimental */ diff --git a/src/v1/unstable/schema/zod.gen.ts b/src/v1/unstable/schema/zod.gen.ts index eb7a1080..c35380f5 100644 --- a/src/v1/unstable/schema/zod.gen.ts +++ b/src/v1/unstable/schema/zod.gen.ts @@ -1774,6 +1774,7 @@ export const zSessionConfigOptionCategory = z.union([ z.literal("mode"), z.literal("model"), z.literal("model_config"), + z.literal("speed"), z.literal("thought_level"), z.string(), ]); @@ -2398,24 +2399,6 @@ export const zPlanItems = z.object({ ), }); -/** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A plan represented by a file URI. - * - * @experimental - */ -export const zPlanFile = z.object({ - planId: zPlanId, - uri: z.string(), - _meta: defaultOnError( - z.record(z.string(), z.unknown()).nullish(), - () => undefined, - ), -}); - /** * **UNSTABLE** * @@ -2428,6 +2411,7 @@ export const zPlanFile = z.object({ export const zPlanMarkdown = z.object({ planId: zPlanId, content: z.string(), + path: defaultOnError(z.string().nullish(), () => undefined), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -2449,11 +2433,6 @@ export const zPlanUpdateContent = z.union([ type: z.literal("items"), }), ), - zPlanFile.and( - z.object({ - type: z.literal("file"), - }), - ), zPlanMarkdown.and( z.object({ type: z.literal("markdown"), @@ -2693,6 +2672,51 @@ export const zCompactionSummaryChunk = z.object({ ), }); +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * A prompt the Agent predicts the user will send next. + * + * @experimental + */ +export const zPromptSuggestion = z.object({ + prompt: z.array(zContentBlock).min(1), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Replaces the session's current list of prompt suggestions. + * + * A prompt suggestion is a prompt the Agent predicts the user will send next. + * The Client offers suggestions where the user writes the next prompt. The + * list in the most recent update is the current list until the Agent sends + * another update. A session starts with an empty list. + * + * Suggestions are not part of session history. Agents SHOULD NOT include + * this update when they replay a session. + * + * Agents MUST only send this update when the Client advertised + * the `session.promptSuggestions` Client capability. + * + * @experimental + */ +export const zPromptSuggestionsUpdate = z.object({ + suggestions: vecSkipError(zPromptSuggestion), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * **UNSTABLE** * @@ -2850,7 +2874,10 @@ export const zRequiresActionStateUpdate = z.object({ * snapshot normally. * * This is not a task outcome or session closure. It does not cancel work, resolve - * pending requests, or revoke capabilities. Capabilities are updated separately. + * pending requests, or revoke capabilities. If a control is no longer available, + * the Agent updates the capabilities explicitly. Unfinished tool calls and + * terminals stay as last reported. The Agent does not invent a final status + * for them, and Clients do not present them as confirmed live activity. * * @experimental */ @@ -3092,6 +3119,11 @@ export const zSessionUpdate = z.union([ sessionUpdate: z.literal("compaction_summary_chunk"), }), ), + zPromptSuggestionsUpdate.and( + z.object({ + sessionUpdate: z.literal("prompt_suggestions_update"), + }), + ), zSubagentUpdate.and( z.object({ sessionUpdate: z.literal("subagent_update"), @@ -3238,6 +3270,94 @@ export const zSessionConfigOptionsCapabilities = z.object({ */ export const zNoticeCapabilities = z.record(z.string(), z.unknown()); +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for image content in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include images. + * + * @experimental + */ +export const zPromptSuggestionsImageCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for audio content in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include audio. + * + * @experimental + */ +export const zPromptSuggestionsAudioCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for embedded resources in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include + * `resource` content blocks. + * + * @experimental + */ +export const zPromptSuggestionsEmbeddedContextCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Client support for `prompt_suggestions_update` session updates. + * + * Supplying `{}` means the client can offer prompt suggestions made of `text` + * and `resource_link` content blocks. The client opts in to other content + * block types with the fields below. They have the same names as the agent's + * prompt capabilities. + * + * @experimental + */ +export const zPromptSuggestionsCapabilities = z.object({ + image: defaultOnError( + zPromptSuggestionsImageCapabilities.nullish(), + () => undefined, + ), + audio: defaultOnError( + zPromptSuggestionsAudioCapabilities.nullish(), + () => undefined, + ), + embeddedContext: defaultOnError( + zPromptSuggestionsEmbeddedContextCapabilities.nullish(), + () => undefined, + ), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * Session-related capabilities supported by the client. */ @@ -3251,6 +3371,10 @@ export const zClientSessionCapabilities = z.object({ () => undefined, ), notices: defaultOnError(zNoticeCapabilities.nullish(), () => undefined), + promptSuggestions: defaultOnError( + zPromptSuggestionsCapabilities.nullish(), + () => undefined, + ), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -3800,6 +3924,10 @@ export const zForkSessionRequest = z.object({ ).optional(), mcpServers: defaultOnNull(z.array(zMcpServer), () => []).optional(), systemPrompt: zSystemPrompt.nullish(), + configOptions: defaultOnNull( + z.array(zInitialSessionConfigOption), + () => [], + ).optional(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -3890,7 +4018,8 @@ export const zSessionStatusRequest = z.object({ * * `session/options` MUST NOT create or resume a session, send `session/update` * notifications, add an entry to `session/list`, or change an existing session. Agents - * SHOULD NOT connect to MCP servers to answer it. + * SHOULD NOT connect to MCP servers to answer it. Agents MAY start their runtime to resolve + * the options. The rules above still hold for it. * * @experimental */ diff --git a/src/v2/acp.test.ts b/src/v2/acp.test.ts index b423fe13..1b5b5627 100644 --- a/src/v2/acp.test.ts +++ b/src/v2/acp.test.ts @@ -3460,3 +3460,737 @@ describe("v2 permission requests on session/cancel and session/close", () => { }); } }); + +describe("schema v2.0.0-alpha.16: replay_complete and stable subagents", () => { + it("ends a history replay with replay_complete on each replayed session", async () => { + const { agent, client, methods, PROTOCOL_VERSION } = stableSdk; + const history: stableSdk.SessionUpdate[] = [ + { + sessionUpdate: "user_message", + messageId: "u1", + content: [{ type: "text", text: "Investigate the parser" }], + }, + { sessionUpdate: "subagent_update", sessionId: "child", title: "Parser" }, + ]; + const childHistory: stableSdk.SessionUpdate[] = [ + { + sessionUpdate: "session_message", + messageId: "received-1", + senderSessionId: "s1", + recipientSessionId: "child", + content: [{ type: "text", text: "Please investigate the parser" }], + }, + ]; + const agentApp = agent() + .onRequest(methods.agent.initialize, () => ({ + protocolVersion: PROTOCOL_VERSION, + info: agentInfo, + })) + .onRequest( + methods.agent.session.resume, + async ({ params, client: peer }) => { + const send = (sessionId: string, update: stableSdk.SessionUpdate) => + peer.notify(methods.client.session.update, { sessionId, update }); + if (params.replayFrom) { + if (!stableSdk.ReplayFrom.isStart(params.replayFrom)) { + // An unsupported cursor replays nothing. + throw RequestError.invalidParams({ + type: params.replayFrom.type, + }); + } + if (params.sessionId === "s1") { + for (const update of history) { + await send("s1", update); + } + // An announced child gets its own marker on its own session. + for (const update of childHistory) { + await send("child", update); + } + await send("child", { sessionUpdate: "replay_complete" }); + } + // Sent once, also when the history is empty. + await send(params.sessionId, { sessionUpdate: "replay_complete" }); + } + await send(params.sessionId, { + sessionUpdate: "state_update", + state: "idle", + }); + return {}; + }, + ); + const received: Record = {}; + const clientApp = client().onNotification( + methods.client.session.update, + ({ params }) => { + (received[params.sessionId] ??= []).push(params.update.sessionUpdate); + }, + ); + + await clientApp.connectWith(agentApp, async (agentContext) => { + await agentContext.request(methods.agent.initialize, { + protocolVersion: PROTOCOL_VERSION, + info: clientInfo, + }); + const replayFrom = { type: "start" as const }; + await agentContext.request(methods.agent.session.resume, { + sessionId: "s1", + cwd: "/work", + replayFrom, + }); + await agentContext.request(methods.agent.session.resume, { + sessionId: "empty", + cwd: "/work", + replayFrom, + }); + await agentContext.request(methods.agent.session.resume, { + sessionId: "plain", + cwd: "/work", + }); + await expect( + agentContext.request(methods.agent.session.resume, { + sessionId: "rejected", + cwd: "/work", + replayFrom: { type: "_cursor", after: "u1" }, + }), + ).rejects.toMatchObject({ code: -32602 }); + await until(() => received.plain?.length === 1); + }); + + // Every update before a session's `replay_complete` is replayed history. + expect(received).toEqual({ + s1: [ + "user_message", + "subagent_update", + "replay_complete", + "state_update", + ], + child: ["session_message", "replay_complete"], + empty: ["replay_complete", "state_update"], + plain: ["state_update"], + }); + }); + + describe.each([ + ["stable", stableZod], + ["unstable", unstableZod], + ] as const)("%s lane", (_lane, zod) => { + const parse = (update: unknown) => zod.zSessionUpdate.parse(update); + + it("parses replay_complete", () => { + expect(parse({ sessionUpdate: "replay_complete" })).toEqual({ + sessionUpdate: "replay_complete", + }); + expect( + parse({ sessionUpdate: "replay_complete", _meta: { cursor: "c1" } }), + ).toEqual({ sessionUpdate: "replay_complete", _meta: { cursor: "c1" } }); + // It has no required fields. Malformed metadata means no metadata. + expect( + parse({ sessionUpdate: "replay_complete", _meta: "oops" }), + ).toEqual({ sessionUpdate: "replay_complete" }); + }); + + it("parses subagent updates and session-directed messages", () => { + const announced = { + sessionUpdate: "subagent_update", + sessionId: "child", + title: "Explore", + capabilities: { cancel: {} }, + state: { + state: "unknown", + background: [{ type: "subagent", sessionId: "grandchild" }], + }, + }; + expect(parse(announced)).toEqual(announced); + const message = { + sessionUpdate: "session_message", + messageId: "sent-1", + senderSessionId: "parent", + recipientSessionId: "child", + recipientMessageId: null, + content: [{ type: "text", text: "Please investigate" }], + }; + expect(parse(message)).toEqual(message); + const chunk = { + sessionUpdate: "session_message_chunk", + messageId: "sent-1", + content: { type: "text", text: " the parser" }, + }; + expect(parse(chunk)).toEqual(chunk); + + // A malformed known update is not preserved as an unknown update. + for (const malformed of [ + { sessionUpdate: "subagent_update" }, + { sessionUpdate: "session_message" }, + { sessionUpdate: "session_message_chunk", messageId: "sent-1" }, + ]) { + expect(() => parse(malformed)).toThrow(); + } + }); + + it("parses the unknown state, subagent background work and session references", () => { + expect( + parse({ + sessionUpdate: "state_update", + state: "unknown", + background: [ + { type: "terminal", terminalId: "t" }, + { type: "subagent", sessionId: "child" }, + ], + }), + ).toEqual({ + sessionUpdate: "state_update", + state: "unknown", + background: [ + { type: "terminal", terminalId: "t" }, + { type: "subagent", sessionId: "child" }, + ], + }); + expect(() => zod.zBackgroundWork.parse({ type: "subagent" })).toThrow(); + + expect( + zod.zToolCallContent.parse({ type: "session", sessionId: "child" }), + ).toEqual({ type: "session", sessionId: "child" }); + expect(() => zod.zToolCallContent.parse({ type: "session" })).toThrow(); + }); + }); + + it("narrows the stable subagent variants from the stable entrypoint", () => { + const announced: stableSdk.SessionUpdate = { + sessionUpdate: "subagent_update", + sessionId: "child", + capabilities: { cancel: {} }, + state: { state: "unknown" }, + }; + expect(stableSdk.SessionUpdate.isSubagentUpdate(announced)).toBe(true); + expect(stableSdk.SessionUpdate.isCustom(announced)).toBe(false); + const message: stableSdk.SessionUpdate = { + sessionUpdate: "session_message", + messageId: "sent-1", + recipientSessionId: "child", + }; + expect(stableSdk.SessionUpdate.isSessionMessage(message)).toBe(true); + const chunk: stableSdk.SessionUpdate = { + sessionUpdate: "session_message_chunk", + messageId: "sent-1", + content: { type: "text", text: "hello" }, + }; + expect(stableSdk.SessionUpdate.isSessionMessageChunk(chunk)).toBe(true); + const complete: stableSdk.SessionUpdate = { + sessionUpdate: "replay_complete", + }; + expect(stableSdk.SessionUpdate.isReplayComplete(complete)).toBe(true); + expect(stableSdk.SessionUpdate.isCustom(complete)).toBe(false); + + const unknown: stableSdk.StateUpdate = { state: "unknown" }; + expect(stableSdk.StateUpdate.isUnknown(unknown)).toBe(true); + expect(stableSdk.StateUpdate.isCustom(unknown)).toBe(false); + const subagent: stableSdk.BackgroundWork = { + type: "subagent", + sessionId: "child", + }; + expect(stableSdk.BackgroundWork.isSubagent(subagent)).toBe(true); + expect(stableSdk.BackgroundWork.isCustom(subagent)).toBe(false); + const reference: stableSdk.ToolCallContent = { + type: "session", + sessionId: "child", + }; + expect(stableSdk.ToolCallContent.isSession(reference)).toBe(true); + expect(stableSdk.ToolCallContent.isCustom(reference)).toBe(false); + }); + + it("keeps the background list of an unknown state on ActiveSession", async () => { + const { agent, client, methods, PROTOCOL_VERSION } = stableSdk; + let peer: stableSdk.AgentContext | undefined; + const agentApp = agent() + .onRequest(methods.agent.initialize, () => ({ + protocolVersion: PROTOCOL_VERSION, + info: agentInfo, + })) + .onRequest(methods.agent.session.new, ({ client: agentClient }) => { + peer = agentClient; + return { sessionId: "s" }; + }); + + await client().connectWith(agentApp, async (agentContext) => { + await agentContext.request(methods.agent.initialize, { + protocolVersion: PROTOCOL_VERSION, + info: clientInfo, + }); + const session = await agentContext.buildSession("/workspace").start(); + try { + await peer!.notify(methods.client.session.update, { + sessionId: "s", + update: { + sessionUpdate: "state_update", + state: "unknown", + background: [{ type: "subagent", sessionId: "child" }], + }, + }); + // `unknown` is not idle, so it is not a `stop` message. + const message = await session.nextUpdate(); + expect(message.kind).toBe("session_update"); + expect(session.background).toEqual([ + { type: "subagent", sessionId: "child" }, + ]); + } finally { + session.dispose(); + } + }); + }); +}); + +describe("schema v2.0.0-alpha.16: unstable skills, prompt suggestions and plan paths", () => { + const skill = { + type: "skill" as const, + name: "pdf-processing", + description: "Extract text and tables from PDF files", + path: "/home/alice/.agents/skills/pdf-processing/SKILL.md", + }; + const command = { + type: "command" as const, + name: "test", + description: "Run project tests", + }; + + it("round-trips skill commands, skill prompts, prompt suggestions and fork config options", async () => { + let clientCapabilities: sdk.ClientCapabilities | undefined; + const prompts: Array = []; + const forks: sdk.ForkSessionRequest[] = []; + const suggestions: sdk.PromptSuggestion[] = [ + { prompt: [{ type: "text", text: "Summarize report.pdf" }] }, + { + prompt: [ + { type: "text", text: "Add tests for " }, + { + type: "resource_link", + name: "parser.rs", + uri: "file:///home/user/project/src/parser.rs", + }, + ], + }, + ]; + const agentApp = agent() + .onRequest(methods.agent.initialize, ({ params }) => { + clientCapabilities = params.capabilities; + return { + protocolVersion: PROTOCOL_VERSION, + info: agentInfo, + // `skill: {}` allows one skill block, as the first block. + capabilities: { + session: { prompt: { skill: {} }, fork: {}, options: {} }, + }, + }; + }) + .onRequest(methods.agent.session.new, () => ({ + sessionId: "s1", + // Skill entries need the client's `session.commands.skill`. + availableCommands: clientCapabilities?.session?.commands?.skill + ? [skill, command] + : [command], + })) + .onRequest(methods.agent.session.fork, ({ params }) => { + forks.push(params); + return { sessionId: "forked" }; + }) + .onRequest( + methods.agent.session.prompt, + async ({ params, client: peer }) => { + const [first, ...rest] = params.prompt; + if ( + rest.some((block) => sdk.ContentBlock.isSkill(block)) || + (sdk.ContentBlock.isSkill(first) && first.name !== skill.name) + ) { + throw RequestError.invalidParams({ type: "skill" }); + } + prompts.push(params.prompt); + if (clientCapabilities?.session?.promptSuggestions) { + await peer.notify(methods.client.session.update, { + sessionId: params.sessionId, + update: { + sessionUpdate: "prompt_suggestions_update", + suggestions, + }, + }); + } + return { messageId: "u1" }; + }, + ); + const updates: SessionUpdate[] = []; + const clientApp = client().onNotification( + methods.client.session.update, + ({ params }) => { + updates.push(params.update); + }, + ); + + await clientApp.connectWith(agentApp, async (agentContext) => { + const initialized = await agentContext.request(methods.agent.initialize, { + protocolVersion: PROTOCOL_VERSION, + info: clientInfo, + capabilities: { + session: { commands: { skill: {} }, promptSuggestions: {} }, + }, + }); + expect(initialized.capabilities?.session?.prompt?.skill).toEqual({}); + + const session = await agentContext.request(methods.agent.session.new, { + cwd: "/work", + }); + expect(session.availableCommands).toEqual([skill, command]); + const [advertised] = session.availableCommands ?? []; + expect(sdk.AvailableCommand.isSkill(advertised)).toBe(true); + expect(sdk.AvailableCommand.isCommand(advertised)).toBe(false); + + // A skill is invoked with a `skill` block, not with `/name`. + const prompt: sdk.PromptRequest["prompt"] = [ + { type: "skill", name: skill.name }, + { type: "text", text: "Summarize report.pdf" }, + ]; + await agentContext.request(methods.agent.session.prompt, { + sessionId: "s1", + prompt, + }); + expect(prompts).toEqual([prompt]); + await expect( + agentContext.request(methods.agent.session.prompt, { + sessionId: "s1", + prompt: [{ type: "skill", name: "unknown-skill" }], + }), + ).rejects.toMatchObject({ code: -32602 }); + // A skill block without a name never reaches the handler. + await expect( + agentContext.request(methods.agent.session.prompt, { + sessionId: "s1", + prompt: [{ type: "skill" }], + } as never), + ).rejects.toMatchObject({ code: -32602 }); + expect(prompts).toHaveLength(1); + + await until(() => updates.length === 1); + const [update] = updates; + expect(update).toEqual({ + sessionUpdate: "prompt_suggestions_update", + suggestions, + }); + if (sdk.SessionUpdate.isPromptSuggestionsUpdate(update)) { + // An accepted suggestion is sent as the next prompt unchanged. + await agentContext.request(methods.agent.session.prompt, { + sessionId: "s1", + prompt: update.suggestions[1].prompt, + }); + } + expect(prompts[1]).toEqual(suggestions[1].prompt); + + const configOptions = [ + { configId: "model", type: "id" as const, value: "model-2" }, + { configId: "fast", type: "boolean" as const, value: true }, + ]; + await agentContext.request(methods.agent.session.fork, { + sessionId: "s1", + cwd: "/work", + configOptions, + }); + expect(forks).toEqual([{ sessionId: "s1", cwd: "/work", configOptions }]); + // A malformed entry fails the request instead of being dropped. + await expect( + agentContext.request(methods.agent.session.fork, { + sessionId: "s1", + cwd: "/work", + configOptions: [{ configId: "fast", type: "boolean", value: "yes" }], + } as never), + ).rejects.toMatchObject({ code: -32602 }); + }); + }); + + it("validates skill commands and skill content blocks", () => { + expect(unstableZod.zAvailableCommand.parse(skill)).toEqual(skill); + // A skill has no input specification. Invalid display fields are absent. + expect( + unstableZod.zAvailableCommand.parse({ + type: "skill", + name: "pdf-processing", + description: "Extract text", + path: 7, + _meta: "oops", + input: { type: "text", hint: "file or instructions" }, + }), + ).toEqual({ + type: "skill", + name: "pdf-processing", + description: "Extract text", + }); + // A malformed skill is skipped and never preserved as an unknown type. + for (const malformed of [ + { type: "skill", name: "pdf-processing" }, + { type: "skill", description: "Extract text" }, + { type: "skill", name: 1, description: "Extract text" }, + ]) { + expect(unstableZod.zAvailableCommand.safeParse(malformed).success).toBe( + false, + ); + } + expect( + unstableZod.zAvailableCommandsUpdate.parse({ + availableCommands: [skill, { type: "skill", name: "broken" }, command], + }).availableCommands, + ).toEqual([skill, command]); + const parsedSkill = unstableZod.zAvailableCommand.parse( + skill, + ) as sdk.AvailableCommand; + expect(sdk.AvailableCommand.isSkill(parsedSkill)).toBe(true); + expect(sdk.AvailableCommand.isCustom(parsedSkill)).toBe(false); + + const reference = unstableZod.zContentBlock.parse({ + type: "skill", + name: "pdf-processing", + _meta: "oops", + }) as sdk.ContentBlock; + expect(reference).toEqual({ type: "skill", name: "pdf-processing" }); + expect(sdk.ContentBlock.isSkill(reference)).toBe(true); + expect(sdk.ContentBlock.isCustom(reference)).toBe(false); + // The Agent acts on `name`, so it stays strict. + for (const malformed of [ + { type: "skill" }, + { type: "skill", name: null }, + { type: "skill", name: 1 }, + ]) { + expect(() => unstableZod.zContentBlock.parse(malformed)).toThrow(); + expect(sdk.ContentBlock.isSkill(malformed as never)).toBe(false); + expect( + unstableZod.zPromptRequest.safeParse({ + sessionId: "s1", + prompt: [malformed], + }).success, + ).toBe(false); + } + // A replayed user message keeps the block. + const replayed = { + sessionUpdate: "user_message", + messageId: "u1", + content: [ + { type: "skill", name: "pdf-processing" }, + { type: "text", text: "Summarize report.pdf" }, + ], + }; + expect(unstableZod.zSessionUpdate.parse(replayed)).toEqual(replayed); + }); + + it("validates the skill and prompt suggestion capabilities", () => { + // `{}` allows one leading skill block. `inline` allows them anywhere. + const prompt = (capabilities: unknown) => + unstableZod.zPromptCapabilities.parse(capabilities).skill; + expect(prompt({ skill: {} })).toEqual({}); + expect(prompt({ skill: { inline: {} } })).toEqual({ inline: {} }); + for (const unsupported of [ + {}, + { skill: null }, + { skill: false }, + { skill: true }, + ]) { + expect(prompt(unsupported) ?? undefined).toBeUndefined(); + } + // A malformed `inline` does not withdraw the first level. + expect(prompt({ skill: { inline: true } })).toEqual({}); + + const session = (capabilities: unknown) => + unstableZod.zClientCapabilities.parse(capabilities).session; + expect(session({ session: { commands: { skill: {} } } })).toEqual({ + commands: { skill: {} }, + }); + // `session` and `commands` only group capabilities. `{}` advertises nothing. + for (const unsupported of [ + {}, + { session: null }, + { session: {} }, + { session: { commands: null } }, + { session: { commands: {} } }, + { session: { commands: true } }, + { session: { commands: { skill: null } } }, + { session: { commands: { skill: true } } }, + { session: { commands: { skill: "supported" } } }, + ]) { + expect(session(unsupported)?.commands?.skill ?? undefined).toBe( + undefined, + ); + } + + expect(session({ session: { promptSuggestions: {} } })).toEqual({ + promptSuggestions: {}, + }); + expect( + session({ + session: { + promptSuggestions: { image: true, audio: {}, embeddedContext: {} }, + }, + }), + ).toEqual({ promptSuggestions: { audio: {}, embeddedContext: {} } }); + // A malformed capability does not withdraw the others in the group. + expect( + session({ + session: { promptSuggestions: true, commands: { skill: {} } }, + }), + ).toEqual({ commands: { skill: {} } }); + }); + + it("validates prompt suggestions", () => { + const parse = (update: unknown) => unstableZod.zSessionUpdate.parse(update); + const text = (text: string) => ({ prompt: [{ type: "text", text }] }); + // Custom and future content blocks are valid v2 content, so they are kept. + const future = { + prompt: [ + { type: "_acme/widget", spec: 1 }, + { type: "hologram", data: "AAAA" }, + ], + }; + + // One bad block fails the whole suggestion. The rest of the list is kept. + const update = parse({ + sessionUpdate: "prompt_suggestions_update", + suggestions: [ + text("Keep the first"), + { text: "A suggestion without a prompt" }, + { prompt: "Not a list" }, + { prompt: [{ type: "text", text: "Open " }, { type: "text" }] }, + { prompt: [{ type: "skill" }] }, + { prompt: [] }, + "not an object", + future, + { ...text("Keep the last"), _meta: false }, + ], + }) as SessionUpdate; + expect(update).toEqual({ + sessionUpdate: "prompt_suggestions_update", + suggestions: [text("Keep the first"), future, text("Keep the last")], + }); + expect(sdk.SessionUpdate.isPromptSuggestionsUpdate(update)).toBe(true); + expect(sdk.SessionUpdate.isCustom(update)).toBe(false); + + // An empty list withdraws the suggestions. + expect( + parse({ sessionUpdate: "prompt_suggestions_update", suggestions: [] }), + ).toEqual({ sessionUpdate: "prompt_suggestions_update", suggestions: [] }); + // The list is required and non-null, and a malformed update is never + // preserved as an unknown update. + for (const malformed of [ + { suggestions: null }, + { suggestions: "none" }, + { suggestions: { prompt: [] } }, + {}, + ]) { + expect(() => + parse({ sessionUpdate: "prompt_suggestions_update", ...malformed }), + ).toThrow(); + } + }); + + it("validates markdown plan paths, the speed category and fork config options", () => { + const markdown = { + type: "markdown", + planId: "plan-1", + content: "# Next steps", + }; + const withPath = unstableZod.zPlanUpdateContent.parse({ + ...markdown, + path: "/home/user/project/PLAN.md", + }) as sdk.PlanUpdateContent; + expect(withPath).toEqual({ + ...markdown, + path: "/home/user/project/PLAN.md", + }); + expect(sdk.PlanUpdateContent.isMarkdown(withPath)).toBe(true); + // `null` and a malformed path both mean the plan has no file. + expect( + unstableZod.zPlanUpdateContent.parse({ ...markdown, path: 42 }), + ).toEqual(markdown); + expect( + unstableZod.zPlanUpdateContent.parse({ ...markdown, path: null }), + ).toEqual({ ...markdown, path: null }); + expect(() => + unstableZod.zPlanUpdateContent.parse({ + type: "markdown", + planId: "plan-1", + }), + ).toThrow(); + // The `file` plan type is gone. It is now preserved like any type this + // SDK does not know. + expect(unstableZod).not.toHaveProperty("zPlanFile"); + expect(sdk.PlanUpdateContent).not.toHaveProperty("isFile"); + const file = unstableZod.zPlanUpdateContent.parse({ + type: "file", + planId: "plan-1", + uri: "file:///tmp/plan.md", + }) as sdk.PlanUpdateContent; + expect(sdk.PlanUpdateContent.isCustom(file)).toBe(true); + + expect( + unstableZod.zSessionConfigOption.parse({ + type: "boolean", + configId: "fast", + name: "Fast mode", + category: "speed", + currentValue: true, + }), + ).toMatchObject({ category: "speed", currentValue: true }); + + // `null` means an empty list. Entries stay strict and need `type`. + expect( + unstableZod.zForkSessionRequest.parse({ + sessionId: "s1", + cwd: "/work", + configOptions: null, + }), + ).toEqual({ sessionId: "s1", cwd: "/work", configOptions: [] }); + expect( + unstableZod.zForkSessionRequest.parse({ sessionId: "s1", cwd: "/work" }), + ).not.toHaveProperty("configOptions"); + for (const entry of [ + { configId: "model", value: "model-2" }, + { type: "boolean", value: true }, + { configId: "fast", type: "boolean", value: "yes" }, + ]) { + expect(() => + unstableZod.zForkSessionRequest.parse({ + sessionId: "s1", + cwd: "/work", + configOptions: [entry], + }), + ).toThrow(); + } + }); + + it("keeps the unstable additions out of the stable entrypoint", () => { + // Types the stable schema does not know are preserved, not validated. + const unknownCommand = stableZod.zAvailableCommand.parse( + skill, + ) as stableSdk.AvailableCommand; + expect(unknownCommand).toEqual(skill); + expect(stableSdk.AvailableCommand).not.toHaveProperty("isSkill"); + expect(stableSdk.AvailableCommand.isCustom(unknownCommand)).toBe(true); + expect(stableSdk.ContentBlock).not.toHaveProperty("isSkill"); + expect(stableZod.zContentBlock.parse({ type: "skill" })).toEqual({ + type: "skill", + }); + expect(stableSdk.SessionUpdate).not.toHaveProperty( + "isPromptSuggestionsUpdate", + ); + expect( + stableZod.zSessionUpdate.parse({ + sessionUpdate: "prompt_suggestions_update", + suggestions: "none", + }), + ).toEqual({ + sessionUpdate: "prompt_suggestions_update", + suggestions: "none", + }); + expect( + stableZod.zClientCapabilities.parse({ + session: { commands: { skill: {} }, promptSuggestions: {} }, + }), + ).not.toHaveProperty("session"); + expect( + stableZod.zPromptCapabilities.parse({ skill: {} }), + ).not.toHaveProperty("skill"); + expect(stableSdk.PlanUpdateContent).not.toHaveProperty("isMarkdown"); + expect(stableSdk.methods.agent.session).not.toHaveProperty("fork"); + }); +}); diff --git a/src/v2/acp.ts b/src/v2/acp.ts index 426d36c0..7ad580fb 100644 --- a/src/v2/acp.ts +++ b/src/v2/acp.ts @@ -17,6 +17,15 @@ * imply idle. Agents may send live traffic after initialization even when no * client request is pending. * + * A `session/resume` with a `replayFrom` cursor replays history as + * `session/update` notifications. The agent ends the replay with one + * `replay_complete` update, after the last replayed update and before the + * current-state snapshot and the response, also when the history is empty. + * Every update for the session before it is replayed history, and every + * update after it is not. A replay that ends without `replay_complete` and + * without a successful response is truncated. Agents do not send it when + * `replayFrom` is omitted or `null`. + * * Top-level `_meta` on upsert updates patches stored metadata by key: omission * leaves it unchanged, `null` clears it, null-valued keys are removed, and other * values replace their key whole without nested merging. An empty object does @@ -1863,6 +1872,10 @@ export class ActiveSession { * `methods.agent.toolCall.cancel` through `ClientContext.request` with the * child's `sessionId`. The tool call's own `capabilities.cancel` enables * that even when the child session doesn't advertise `cancel`. + * + * The Agent answers `-32602` once the tool call has a final status. This + * includes a request sent again after the first one was accepted. The + * `tool_call_update` with that status reports the outcome. */ cancelToolCall( toolCallId: schema.ToolCallId, @@ -1896,6 +1909,10 @@ export class ActiveSession { * `methods.agent.terminal.cancel` through `ClientContext.request` with the * child's `sessionId`. The terminal's own `capabilities.cancel` enables * that even when the child session doesn't advertise `cancel`. + * + * The Agent answers `-32602` once the process has exited. This includes a + * request sent again after the first one was accepted. The + * `terminal_update` that sets `exitStatus` reports the outcome. */ cancelTerminal( terminalId: schema.TerminalId, @@ -2636,9 +2653,12 @@ export type AgentRequestHandlersByMethod = { /** * Handles `session/options`. Respond with the config options and commands * a session would start with. With `sessionId`, respond with what - * `session/resume` would return for that session. It must not create, - * resume, or change a session, send `session/update` notifications, or add - * an entry to `session/list`. An unknown session gets `-32002`. + * `session/resume` would return for that session. Clients also send the + * source session's `sessionId` to show the options before a + * `session/fork`. It must not create, resume, or change a session, send + * `session/update` notifications, or add an entry to `session/list`. The + * agent may start its runtime to resolve the options, and these rules + * still hold for it. An unknown session gets `-32002`. */ [schema.AGENT_METHODS.session_options]: AgentRequestHandler< schema.SessionOptionsRequest, @@ -2653,6 +2673,21 @@ export type AgentRequestHandlersByMethod = { schema.SetSessionTitleRequest, schema.SetSessionTitleResponse >; + /** + * Handles `session/resume`. + * + * With a `replayFrom` cursor, replay the history as `session/update` + * notifications, then send `replay_complete` exactly once, also when the + * history is empty. Until then, send only replayed history for the + * session and hold its other updates. Requests such as + * `session/request_permission` should wait too. Do not send + * `replay_complete` when `replayFrom` is omitted or `null`, or when the + * replay cannot be finished. Throw `-32602` for an unsupported cursor, + * without replaying anything. + * + * For every successful resume, send the current `state_update` before + * responding, after `replay_complete` when a replay was requested. + */ [schema.AGENT_METHODS.session_resume]: AgentRequestHandler< schema.ResumeSessionRequest, schema.ResumeSessionResponse @@ -2675,8 +2710,10 @@ export type AgentRequestHandlersByMethod = { * * Respond once the cancellation is accepted, then confirm it with a * `tool_call_update` to a final status. Throw `-32602` for a tool call - * that is unknown, already final, or not cancellable. Without this handler - * (and without a `"*"` request fallback), the app answers every + * that is unknown, already final, or not cancellable. This includes a + * request the Client sends again after the first one was accepted and the + * tool call has since reached a final status. Without this handler (and + * without a `"*"` request fallback), the app answers every * `tool_call/cancel` with `-32602`. */ [schema.AGENT_METHODS.tool_call_cancel]: AgentRequestHandler< @@ -2689,13 +2726,20 @@ export type AgentRequestHandlersByMethod = { * Respond once the cancellation is accepted, then confirm it with a * `terminal_update` that sets `exitStatus`. Cancelling does not release the * terminal. Throw `-32602` for a terminal that is unknown, has already - * exited, or is not cancellable. Without this handler (and without a `"*"` - * request fallback), the app answers every `terminal/cancel` with `-32602`. + * exited, or is not cancellable. This includes a request the Client sends + * again after the first one was accepted and the process has since exited. + * Without this handler (and without a `"*"` request fallback), the app + * answers every `terminal/cancel` with `-32602`. */ [schema.AGENT_METHODS.terminal_cancel]: AgentRequestHandler< schema.CancelTerminalRequest, schema.CancelTerminalResponse | void >; + /** + * Handles `auth/logout`. Logging out is idempotent: respond with the same + * empty result when there is no authenticated state to end, for example + * because the Client already logged out. + */ [schema.AGENT_METHODS.auth_logout]: AgentRequestHandler< schema.LogoutAuthRequest, schema.LogoutAuthResponse | void diff --git a/src/v2/schema/guards.gen.ts b/src/v2/schema/guards.gen.ts index d449026e..a76445af 100644 --- a/src/v2/schema/guards.gen.ts +++ b/src/v2/schema/guards.gen.ts @@ -33,6 +33,9 @@ const zGuardToolCallContentDiff = validate.zDiff const zGuardToolCallContentTerminal = validate.zTerminal.and( z.object({ type: z.literal("terminal") }), ); +const zGuardToolCallContentSession = validate.zSessionReference.and( + z.object({ type: z.literal("session") }), +); const zGuardContentBlockText = validate.zTextContent .and(z.object({ annotations: validate.zAnnotations.nullish() })) .and(z.object({ type: z.literal("text") })); @@ -199,12 +202,40 @@ const zGuardSessionUpdateCompactionSummaryChunk = validate.zCompactionSummaryChunk.and( z.object({ sessionUpdate: z.literal("compaction_summary_chunk") }), ); +const zGuardSessionUpdateReplayComplete = validate.zReplayComplete.and( + z.object({ sessionUpdate: z.literal("replay_complete") }), +); +const zGuardSessionUpdateSubagentUpdate = validate.zSubagentUpdate + .and( + z.object({ + capabilities: validate.zSubagentSessionCapabilities.nullish(), + state: validate.zStateUpdate.nullish(), + }), + ) + .and(z.object({ sessionUpdate: z.literal("subagent_update") })); +const zGuardSessionUpdateSessionMessage = validate.zSessionMessage + .and( + z.object({ + senderSessionId: validate.zSessionId.nullish(), + recipientSessionId: validate.zSessionId.nullish(), + senderMessageId: validate.zMessageId.nullish(), + recipientMessageId: validate.zMessageId.nullish(), + }), + ) + .and(z.object({ sessionUpdate: z.literal("session_message") })); +const zGuardSessionUpdateSessionMessageChunk = + validate.zSessionMessageChunk.and( + z.object({ sessionUpdate: z.literal("session_message_chunk") }), + ); const zGuardBackgroundWorkTerminal = validate.zBackgroundTerminal.and( z.object({ type: z.literal("terminal") }), ); const zGuardBackgroundWorkToolCall = validate.zBackgroundToolCall.and( z.object({ type: z.literal("tool_call") }), ); +const zGuardBackgroundWorkSubagent = validate.zBackgroundSubagent.and( + z.object({ type: z.literal("subagent") }), +); const zGuardIdleStateUpdateEndTurn = z.object({ stopReason: z.literal("end_turn"), }); @@ -233,6 +264,9 @@ const zGuardStateUpdateIdle = validate.zIdleStateUpdate.and( const zGuardStateUpdateRequiresAction = validate.zRequiresActionStateUpdate.and( z.object({ state: z.literal("requires_action") }), ); +const zGuardStateUpdateUnknown = validate.zUnknownStateUpdate.and( + z.object({ state: z.literal("unknown") }), +); const zGuardPlanUpdateContentItems = validate.zPlanItems.and( z.object({ type: z.literal("items") }), ); @@ -401,6 +435,16 @@ export const ToolCallContent = { ); }, + /** Narrow to the `session` variant, validating its payload. */ + isSession( + value: types.ToolCallContent, + ): value is types.SessionReference & { type: "session" } { + return ( + tagOf(value, "type") === "session" && + zGuardToolCallContentSession.safeParse(value).success + ); + }, + /** * Narrow to a custom or future variant: the `type` tag matches no known variant. * @@ -415,7 +459,8 @@ export const ToolCallContent = { | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return ( - typeof tag === "string" && !["content", "diff", "terminal"].includes(tag) + typeof tag === "string" && + !["content", "diff", "session", "terminal"].includes(tag) ); }, } as const; @@ -1319,6 +1364,48 @@ export const SessionUpdate = { ); }, + /** Narrow to the `replay_complete` variant, validating its payload. */ + isReplayComplete( + value: types.SessionUpdate, + ): value is types.ReplayComplete & { sessionUpdate: "replay_complete" } { + return ( + tagOf(value, "sessionUpdate") === "replay_complete" && + zGuardSessionUpdateReplayComplete.safeParse(value).success + ); + }, + + /** Narrow to the `subagent_update` variant, validating its payload. */ + isSubagentUpdate( + value: types.SessionUpdate, + ): value is types.SubagentUpdate & { sessionUpdate: "subagent_update" } { + return ( + tagOf(value, "sessionUpdate") === "subagent_update" && + zGuardSessionUpdateSubagentUpdate.safeParse(value).success + ); + }, + + /** Narrow to the `session_message` variant, validating its payload. */ + isSessionMessage( + value: types.SessionUpdate, + ): value is types.SessionMessage & { sessionUpdate: "session_message" } { + return ( + tagOf(value, "sessionUpdate") === "session_message" && + zGuardSessionUpdateSessionMessage.safeParse(value).success + ); + }, + + /** Narrow to the `session_message_chunk` variant, validating its payload. */ + isSessionMessageChunk( + value: types.SessionUpdate, + ): value is types.SessionMessageChunk & { + sessionUpdate: "session_message_chunk"; + } { + return ( + tagOf(value, "sessionUpdate") === "session_message_chunk" && + zGuardSessionUpdateSessionMessageChunk.safeParse(value).success + ); + }, + /** * Narrow to a custom or future variant: the `sessionUpdate` tag matches no known variant. * @@ -1345,8 +1432,12 @@ export const SessionUpdate = { "config_option_update", "notice", "plan_update", + "replay_complete", "session_info_update", + "session_message", + "session_message_chunk", "state_update", + "subagent_update", "terminal_output_chunk", "terminal_update", "tool_call_content_chunk", @@ -1406,6 +1497,16 @@ export const BackgroundWork = { ); }, + /** Narrow to the `subagent` variant, validating its payload. */ + isSubagent( + value: types.BackgroundWork, + ): value is types.BackgroundSubagent & { type: "subagent" } { + return ( + tagOf(value, "type") === "subagent" && + zGuardBackgroundWorkSubagent.safeParse(value).success + ); + }, + /** * Narrow to a custom or future variant: the `type` tag matches no known variant. * @@ -1419,7 +1520,10 @@ export const BackgroundWork = { | { type: `_${string}`; [key: string]: unknown } | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); - return typeof tag === "string" && !["terminal", "tool_call"].includes(tag); + return ( + typeof tag === "string" && + !["subagent", "terminal", "tool_call"].includes(tag) + ); }, } as const; @@ -1658,6 +1762,16 @@ export const StateUpdate = { ); }, + /** Narrow to the `unknown` variant, validating its payload. */ + isUnknown( + value: types.StateUpdate, + ): value is types.UnknownStateUpdate & { state: "unknown" } { + return ( + tagOf(value, "state") === "unknown" && + zGuardStateUpdateUnknown.safeParse(value).success + ); + }, + /** * Narrow to a custom or future variant: the `state` tag matches no known variant. * @@ -1673,7 +1787,7 @@ export const StateUpdate = { const tag = tagOf(value, "state"); return ( typeof tag === "string" && - !["idle", "requires_action", "running"].includes(tag) + !["idle", "requires_action", "running", "unknown"].includes(tag) ); }, } as const; diff --git a/src/v2/schema/index.ts b/src/v2/schema/index.ts index c973316b..0bd4fd54 100644 --- a/src/v2/schema/index.ts +++ b/src/v2/schema/index.ts @@ -18,6 +18,7 @@ export type { AvailableCommand, AvailableCommandInput, AvailableCommandsUpdate, + BackgroundSubagent, BackgroundTerminal, BackgroundToolCall, BackgroundWork, @@ -131,6 +132,7 @@ export type { PromptRequest, PromptResponse, ProtocolVersion, + ReplayComplete, ReplayFrom, ReplayFromStart, RequestId, @@ -146,6 +148,7 @@ export type { RunningStateUpdate, SelectedPermissionOutcome, SessionAdditionalDirectoriesCapabilities, + SessionCancelCapabilities, SessionCapabilities, SessionConfigBoolean, SessionConfigGroupId, @@ -162,7 +165,10 @@ export type { SessionInfo, SessionInfoUpdate, SessionListCursor, + SessionMessage, + SessionMessageChunk, SessionNotFoundErrorData, + SessionReference, SessionSystemPromptCapabilities, SessionSystemPromptModeCapabilities, SessionSystemPromptOverrideCapabilities, @@ -173,6 +179,8 @@ export type { StringFormat, StringMultiSelectItems, StringPropertySchema, + SubagentSessionCapabilities, + SubagentUpdate, SystemPrompt, SystemPromptMode, Terminal, @@ -199,6 +207,7 @@ export type { ToolCallStatus, ToolCallUpdate, ToolKind, + UnknownStateUpdate, UnsupportedProtocolVersionErrorData, UpdateSessionNotification, UsageUpdate, diff --git a/src/v2/schema/types.gen.ts b/src/v2/schema/types.gen.ts index 966152dc..4bc03cb4 100644 --- a/src/v2/schema/types.gen.ts +++ b/src/v2/schema/types.gen.ts @@ -295,6 +295,9 @@ export type ToolCallContent = | (Terminal & { type: "terminal"; }) + | (SessionReference & { + type: "session"; + }) | ( | { /** @@ -936,6 +939,32 @@ export type Terminal = { } | null; }; +/** + * A display-only reference to another session that this tool call operates on, + * such as a subagent it created, delegated to, or is waiting on. + * + * The referenced session must already be known to the Client through ordinary + * session setup or a `subagent_update` announcement. The reference does not + * establish ownership, grant any Client-initiated controls, or report that the + * referenced session received or finished any work. + */ +export type SessionReference = { + /** + * The referenced session. + */ + sessionId: SessionId; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * A file location being accessed or modified by a tool. * @@ -2689,8 +2718,7 @@ export type AvailableCommand = */ type: `_${string}`; /** - * Command name without the leading `/`. MUST NOT contain whitespace and MUST be unique - * within the command list. + * Command name. MUST NOT contain whitespace and MUST be unique within the command list. */ name: string; /** @@ -2709,8 +2737,7 @@ export type AvailableCommand = */ type: string; /** - * Command name without the leading `/`. MUST NOT contain whitespace and MUST be unique - * within the command list. + * Command name. MUST NOT contain whitespace and MUST be unique within the command list. */ name: string; /** @@ -3272,6 +3299,18 @@ export type SessionUpdate = | (CompactionSummaryChunk & { sessionUpdate: "compaction_summary_chunk"; }) + | (ReplayComplete & { + sessionUpdate: "replay_complete"; + }) + | (SubagentUpdate & { + sessionUpdate: "subagent_update"; + }) + | (SessionMessage & { + sessionUpdate: "session_message"; + }) + | (SessionMessageChunk & { + sessionUpdate: "session_message_chunk"; + }) | ( | { /** @@ -3459,6 +3498,9 @@ export type BackgroundWork = | (BackgroundToolCall & { type: "tool_call"; }) + | (BackgroundSubagent & { + type: "subagent"; + }) | ( | { /** @@ -3524,6 +3566,26 @@ export type BackgroundToolCall = { } | null; }; +/** + * A listed subagent session associated with this session through `subagent_update`. + */ +export type BackgroundSubagent = { + /** + * The ID of the listed session. + */ + sessionId: SessionId; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * Foreground work is in progress. */ @@ -3686,6 +3748,44 @@ export type RequiresActionStateUpdate = { } | null; }; +/** + * The Agent cannot currently determine foreground activity. + * + * Report this when activity becomes unobservable, not merely because the session + * has been quiet. The Client MUST stop presenting the previous state as confirmed + * current activity, but may retain it as last known. A later state replaces this + * snapshot normally. + * + * This is not a task outcome or session closure. It does not cancel work, resolve + * pending requests, or revoke capabilities. If a control is no longer available, + * the Agent updates the capabilities explicitly. Unfinished tool calls and + * terminals stay as last reported. The Agent does not invent a final status + * for them, and Clients do not present them as confirmed live activity. + */ +export type UnknownStateUpdate = { + /** + * Work running independently of foreground work, as a complete snapshot. + * + * Optional. Omitted and `null` are equivalent and mean the Agent is not reporting + * background work, not that nothing is running. `[]` means no background work is + * running. An Agent that reports background work MUST include it on every state + * update for the session. A malformed list is treated as not reported. + * + * See protocol docs: [Background Work](https://agentclientprotocol.com/protocol/v2/prompt-lifecycle#background-work) + */ + background?: Array | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * A snapshot of the session's aggregate foreground activity. * @@ -3738,6 +3838,9 @@ export type StateUpdate = | (RequiresActionStateUpdate & { state: "requires_action"; }) + | (UnknownStateUpdate & { + state: "unknown"; + }) | ( | { /** @@ -4390,6 +4493,261 @@ export type CompactionSummaryChunk = { } | null; }; +/** + * Marks the end of the history replay requested by `session/resume`. + * + * The Agent sends this once on a session for each `session/resume` request + * with a `replayFrom` cursor, after the last replayed update for that session + * and before the response. It sends it for an empty history too. Every update + * for the session between the request and this update is replayed history. + * Every update after it is not. + * + * The Agent does not send it when `replayFrom` is omitted or `null`, or when + * it cannot finish the replay. A resume that ends without it delivered a + * truncated replay. + * + * See protocol docs: [Replay Complete](https://agentclientprotocol.com/protocol/v2/session-setup#replay-complete) + */ +export type ReplayComplete = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * Client-initiated session mutations permitted for a specific subagent session. + * + * A mutation requires an explicit per-child capability. Support for the method + * on ordinary sessions does not grant support on a child. + */ +export type SubagentSessionCapabilities = { + /** + * Permits the client to cancel this child's current work without ending + * the session. Omitted and `null` are equivalent and mean unsupported. An object (including + * `{}`) means supported. + */ + cancel?: SessionCancelCapabilities | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * Capability to cancel work in a subagent session without ending that session. + * + * Supplying `{}` advertises support. An omitted or `null` `cancel` does not. + */ +export type SessionCancelCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * Notification that the enclosing parent session created and owns a child session. + * + * Later updates modify the existing association's metadata, not its ownership. + * + * Sent on the immediate parent session. The first update for an unknown + * `sessionId` announces the child and MUST be sent + * before any live request or notification bearing the child's session ID, + * including messages naming it as sender or recipient. Child events are + * delivered automatically. No separate child load, resume, or subscription is needed. + * Understanding this update, registering child sessions, and applying their + * operation restrictions are baseline v2 requirements. No Client capability + * is required. + * + * Only `sessionId` is required. Other fields have + * patch semantics: omitted fields leave the stored value unchanged, `null` + * clears or unsets the value, and concrete values replace it. A concrete + * `_meta` object instead patches the stored metadata by top-level key. For + * `state`, `null` removes the current state report and leaves activity + * unconfirmed. It does not report idle or cancellation. A child whose capabilities are + * unset permits no Client-initiated session mutations. + * + * `cwd` and `additionalDirectories` report where the Agent placed the child. + * While unset, each inherits the immediate parent's effective value. + * The title and description provide the parent's display metadata for the + * child. Agents SHOULD mirror known child state changes in the parent's + * `state` using the same `StateUpdate` snapshot as the + * ordinary `state_update` notification on the child session. This reports + * child state, not a separate parent-owned lifecycle. Consumers should + * treat duplicate reports idempotently. + * Completing or cancelling work does not end the association: the parent may + * message the same child again. Individual messages and their outcomes do not + * change this association. + */ +export type SubagentUpdate = { + /** + * The opaque session ID identifying the child in all ACP messages. + */ + sessionId: SessionId; + /** + * The parent's human-readable display title for this child. It need not be unique. + * + * Optional and nullable. Omitted means unchanged. `null` clears it. If no + * title is set, the Client chooses a fallback presentation. + */ + title?: string | null; + /** + * The parent's human-readable description of the child's role or purpose. + * + * Optional and nullable. Omitted means unchanged. `null` clears it. This is + * current display metadata, not the history of instructions sent to the child. + */ + description?: string | null; + /** + * Client-initiated session mutations permitted for this subagent session. + * + * Read-only operations retain their normal protocol semantics and + * capability requirements. + */ + capabilities?: SubagentSessionCapabilities | null; + /** + * The child's current foreground state, mirrored onto its parent association. + * + * Optional and nullable. Omitted means unchanged. `null` removes the + * current report and leaves activity unconfirmed (not idle or cancelled). + * A concrete `StateUpdate` replaces the entire previous snapshot. + */ + state?: StateUpdate | null; + /** + * The child's working directory. Must be an absolute path. + * + * Optional and nullable. Omitted means unchanged. `null` clears the + * reported value. While unset, the child inherits its immediate parent's + * effective working directory. + */ + cwd?: AbsolutePath | null; + /** + * Additional workspace roots for the child. Each path must be absolute. + * + * Optional and nullable. Omitted means unchanged. `null` clears the + * reported value. While unset, the child inherits its immediate parent's + * effective additional directories, independently of `cwd`. A concrete + * array replaces the whole list. `[]` reports no additional directories. + */ + additionalDirectories?: Array | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted leaves the stored metadata unchanged. `null` + * clears it. An object patches the stored metadata by top-level key: keys it omits are + * unchanged, a key set to `null` is removed, and any other value replaces that key's + * stored value without merging nested objects. Beyond this patch rule, implementations + * MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * An upsert for an inter-session message. + */ +export type SessionMessage = { + /** + * Identifier of this message within the enclosing session's transcript. + */ + messageId: MessageId; + /** + * The sending session. + * + * Omitted leaves the stored value unchanged. `null` clears it. A value replaces it. + */ + senderSessionId?: SessionId | null; + /** + * The receiving session. + * + * Omitted leaves the stored value unchanged. `null` clears it. A value replaces it. + */ + recipientSessionId?: SessionId | null; + /** + * On an incoming view, the `messageId` of the same message's outgoing view in the + * sender's transcript. + * + * Omitted leaves the stored value unchanged. `null` clears it. A value replaces it. It can arrive + * after the message starts streaming. + */ + senderMessageId?: MessageId | null; + /** + * On an outgoing view, the `messageId` of the same message's incoming view in the + * recipient's transcript. + * + * Omitted leaves the stored value unchanged. `null` clears it. A value replaces it. It can arrive + * after the message starts streaming. + */ + recipientMessageId?: MessageId | null; + /** + * Omitted leaves content unchanged. `null` clears it. A concrete array + * replaces the whole content collection. + */ + content?: Array | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted leaves the stored metadata unchanged. `null` + * clears it. An object patches the stored metadata by top-level key: keys it omits are + * unchanged, a key set to `null` is removed, and any other value replaces that key's + * stored value without merging nested objects. Beyond this patch rule, implementations + * MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Metadata Patches](https://agentclientprotocol.com/protocol/v2/extensibility#metadata-patches) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * A streamed content block of an inter-session message. + * + * A chunk carries only content. The message's participants and references come from + * `session_message` upserts, which can arrive before, between, or after its chunks. + */ +export type SessionMessageChunk = { + /** + * Identifier of this message within the enclosing session's transcript. + */ + messageId: MessageId; + /** + * A single content block appended to the message. + */ + content: ContentBlock; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * This metadata is scoped to the chunk and does not patch stored metadata. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * Notification sent by the agent when a URL-based elicitation is complete. * diff --git a/src/v2/schema/zod.gen.ts b/src/v2/schema/zod.gen.ts index d8836bfc..698d1805 100644 --- a/src/v2/schema/zod.gen.ts +++ b/src/v2/schema/zod.gen.ts @@ -445,6 +445,23 @@ export const zTerminal = z.object({ ), }); +/** + * A display-only reference to another session that this tool call operates on, + * such as a subagent it created, delegated to, or is waiting on. + * + * The referenced session must already be known to the Client through ordinary + * session setup or a `subagent_update` announcement. The reference does not + * establish ownership, grant any Client-initiated controls, or report that the + * referenced session received or finished any work. + */ +export const zSessionReference = z.object({ + sessionId: zSessionId, + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * Content produced by a tool call. * @@ -474,16 +491,21 @@ export const zToolCallContent = preserveCustomPayload( type: z.literal("terminal"), }), ), + zSessionReference.and( + z.object({ + type: z.literal("session"), + }), + ), excludeKnownTags( z.object({ type: z.string(), }), "type", - ["content", "diff", "terminal"], + ["content", "diff", "session", "terminal"], ), ]), "type", - ["content", "diff", "terminal"], + ["content", "diff", "session", "terminal"], ); /** @@ -2062,6 +2084,17 @@ export const zBackgroundToolCall = z.object({ ), }); +/** + * A listed subagent session associated with this session through `subagent_update`. + */ +export const zBackgroundSubagent = z.object({ + sessionId: zSessionId, + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * One piece of work running independently of the session's foreground work. * @@ -2088,16 +2121,21 @@ export const zBackgroundWork = preserveCustomPayload( type: z.literal("tool_call"), }), ), + zBackgroundSubagent.and( + z.object({ + type: z.literal("subagent"), + }), + ), excludeKnownTags( z.object({ type: z.string(), }), "type", - ["terminal", "tool_call"], + ["subagent", "terminal", "tool_call"], ), ]), "type", - ["terminal", "tool_call"], + ["subagent", "terminal", "tool_call"], ); /** @@ -2223,6 +2261,31 @@ export const zRequiresActionStateUpdate = z.object({ ), }); +/** + * The Agent cannot currently determine foreground activity. + * + * Report this when activity becomes unobservable, not merely because the session + * has been quiet. The Client MUST stop presenting the previous state as confirmed + * current activity, but may retain it as last known. A later state replaces this + * snapshot normally. + * + * This is not a task outcome or session closure. It does not cancel work, resolve + * pending requests, or revoke capabilities. If a control is no longer available, + * the Agent updates the capabilities explicitly. Unfinished tool calls and + * terminals stay as last reported. The Agent does not invent a final status + * for them, and Clients do not present them as confirmed live activity. + */ +export const zUnknownStateUpdate = z.object({ + background: defaultOnError( + z.array(zBackgroundWork).nullish(), + () => undefined, + ), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * A snapshot of the session's aggregate foreground activity. * @@ -2286,16 +2349,21 @@ export const zStateUpdate = preserveCustomPayload( state: z.literal("requires_action"), }), ), + zUnknownStateUpdate.and( + z.object({ + state: z.literal("unknown"), + }), + ), excludeKnownTags( z.object({ state: z.string(), }), "state", - ["idle", "requires_action", "running"], + ["idle", "requires_action", "running", "unknown"], ), ]), "state", - ["idle", "requires_action", "running"], + ["idle", "requires_action", "running", "unknown"], ); /** @@ -2672,6 +2740,139 @@ export const zCompactionSummaryChunk = z.object({ ), }); +/** + * Marks the end of the history replay requested by `session/resume`. + * + * The Agent sends this once on a session for each `session/resume` request + * with a `replayFrom` cursor, after the last replayed update for that session + * and before the response. It sends it for an empty history too. Every update + * for the session between the request and this update is replayed history. + * Every update after it is not. + * + * The Agent does not send it when `replayFrom` is omitted or `null`, or when + * it cannot finish the replay. A resume that ends without it delivered a + * truncated replay. + * + * See protocol docs: [Replay Complete](https://agentclientprotocol.com/protocol/v2/session-setup#replay-complete) + */ +export const zReplayComplete = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * Capability to cancel work in a subagent session without ending that session. + * + * Supplying `{}` advertises support. An omitted or `null` `cancel` does not. + */ +export const zSessionCancelCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * Client-initiated session mutations permitted for a specific subagent session. + * + * A mutation requires an explicit per-child capability. Support for the method + * on ordinary sessions does not grant support on a child. + */ +export const zSubagentSessionCapabilities = z.object({ + cancel: defaultOnError(zSessionCancelCapabilities.nullish(), () => undefined), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * Notification that the enclosing parent session created and owns a child session. + * + * Later updates modify the existing association's metadata, not its ownership. + * + * Sent on the immediate parent session. The first update for an unknown + * `sessionId` announces the child and MUST be sent + * before any live request or notification bearing the child's session ID, + * including messages naming it as sender or recipient. Child events are + * delivered automatically. No separate child load, resume, or subscription is needed. + * Understanding this update, registering child sessions, and applying their + * operation restrictions are baseline v2 requirements. No Client capability + * is required. + * + * Only `sessionId` is required. Other fields have + * patch semantics: omitted fields leave the stored value unchanged, `null` + * clears or unsets the value, and concrete values replace it. A concrete + * `_meta` object instead patches the stored metadata by top-level key. For + * `state`, `null` removes the current state report and leaves activity + * unconfirmed. It does not report idle or cancellation. A child whose capabilities are + * unset permits no Client-initiated session mutations. + * + * `cwd` and `additionalDirectories` report where the Agent placed the child. + * While unset, each inherits the immediate parent's effective value. + * The title and description provide the parent's display metadata for the + * child. Agents SHOULD mirror known child state changes in the parent's + * `state` using the same `StateUpdate` snapshot as the + * ordinary `state_update` notification on the child session. This reports + * child state, not a separate parent-owned lifecycle. Consumers should + * treat duplicate reports idempotently. + * Completing or cancelling work does not end the association: the parent may + * message the same child again. Individual messages and their outcomes do not + * change this association. + */ +export const zSubagentUpdate = z.object({ + sessionId: zSessionId, + title: defaultOnError(z.string().nullish(), () => undefined), + description: defaultOnError(z.string().nullish(), () => undefined), + capabilities: defaultOnError( + zSubagentSessionCapabilities.nullish(), + () => undefined, + ), + state: defaultOnError(zStateUpdate.nullish(), () => undefined), + cwd: zAbsolutePath.nullish(), + additionalDirectories: z.array(zAbsolutePath).nullish(), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * An upsert for an inter-session message. + */ +export const zSessionMessage = z.object({ + messageId: zMessageId, + senderSessionId: defaultOnError(zSessionId.nullish(), () => undefined), + recipientSessionId: defaultOnError(zSessionId.nullish(), () => undefined), + senderMessageId: defaultOnError(zMessageId.nullish(), () => undefined), + recipientMessageId: defaultOnError(zMessageId.nullish(), () => undefined), + content: defaultOnError( + vecSkipError(zContentBlock).nullish(), + () => undefined, + ), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * A streamed content block of an inter-session message. + * + * A chunk carries only content. The message's participants and references come from + * `session_message` upserts, which can arrive before, between, or after its chunks. + */ +export const zSessionMessageChunk = z.object({ + messageId: zMessageId, + content: zContentBlock, + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * Different types of updates that can be sent while a session exists. * @@ -2780,6 +2981,26 @@ export const zSessionUpdate = preserveCustomPayload( sessionUpdate: z.literal("compaction_summary_chunk"), }), ), + zReplayComplete.and( + z.object({ + sessionUpdate: z.literal("replay_complete"), + }), + ), + zSubagentUpdate.and( + z.object({ + sessionUpdate: z.literal("subagent_update"), + }), + ), + zSessionMessage.and( + z.object({ + sessionUpdate: z.literal("session_message"), + }), + ), + zSessionMessageChunk.and( + z.object({ + sessionUpdate: z.literal("session_message_chunk"), + }), + ), excludeKnownTags( z.object({ sessionUpdate: z.string(), @@ -2796,8 +3017,12 @@ export const zSessionUpdate = preserveCustomPayload( "config_option_update", "notice", "plan_update", + "replay_complete", "session_info_update", + "session_message", + "session_message_chunk", "state_update", + "subagent_update", "terminal_output_chunk", "terminal_update", "tool_call_content_chunk", @@ -2820,8 +3045,12 @@ export const zSessionUpdate = preserveCustomPayload( "config_option_update", "notice", "plan_update", + "replay_complete", "session_info_update", + "session_message", + "session_message_chunk", "state_update", + "subagent_update", "terminal_output_chunk", "terminal_update", "tool_call_content_chunk", diff --git a/src/v2/stable/acp.ts b/src/v2/stable/acp.ts index 572522a4..5c8a58e2 100644 --- a/src/v2/stable/acp.ts +++ b/src/v2/stable/acp.ts @@ -18,6 +18,15 @@ * imply idle. Agents may send live traffic after initialization even when no * client request is pending. * + * A `session/resume` with a `replayFrom` cursor replays history as + * `session/update` notifications. The agent ends the replay with one + * `replay_complete` update, after the last replayed update and before the + * current-state snapshot and the response, also when the history is empty. + * Every update for the session before it is replayed history, and every + * update after it is not. A replay that ends without `replay_complete` and + * without a successful response is truncated. Agents do not send it when + * `replayFrom` is omitted or `null`. + * * Top-level `_meta` on upsert updates patches stored metadata by key: omission * leaves it unchanged, `null` clears it, null-valued keys are removed, and other * values replace their key whole without nested merging. An empty object does @@ -1721,6 +1730,10 @@ export class ActiveSession { * `methods.agent.toolCall.cancel` through `ClientContext.request` with the * child's `sessionId`. The tool call's own `capabilities.cancel` enables * that even when the child session doesn't advertise `cancel`. + * + * The Agent answers `-32602` once the tool call has a final status. This + * includes a request sent again after the first one was accepted. The + * `tool_call_update` with that status reports the outcome. */ cancelToolCall( toolCallId: schema.ToolCallId, @@ -1753,6 +1766,10 @@ export class ActiveSession { * `methods.agent.terminal.cancel` through `ClientContext.request` with the * child's `sessionId`. The terminal's own `capabilities.cancel` enables * that even when the child session doesn't advertise `cancel`. + * + * The Agent answers `-32602` once the process has exited. This includes a + * request sent again after the first one was accepted. The + * `terminal_update` that sets `exitStatus` reports the outcome. */ cancelTerminal( terminalId: schema.TerminalId, @@ -2311,6 +2328,21 @@ export type AgentRequestHandlersByMethod = { schema.DeleteSessionRequest, schema.DeleteSessionResponse | void >; + /** + * Handles `session/resume`. + * + * With a `replayFrom` cursor, replay the history as `session/update` + * notifications, then send `replay_complete` exactly once, also when the + * history is empty. Until then, send only replayed history for the + * session and hold its other updates. Requests such as + * `session/request_permission` should wait too. Do not send + * `replay_complete` when `replayFrom` is omitted or `null`, or when the + * replay cannot be finished. Throw `-32602` for an unsupported cursor, + * without replaying anything. + * + * For every successful resume, send the current `state_update` before + * responding, after `replay_complete` when a replay was requested. + */ [schema.AGENT_METHODS.session_resume]: AgentRequestHandler< schema.ResumeSessionRequest, schema.ResumeSessionResponse @@ -2333,8 +2365,10 @@ export type AgentRequestHandlersByMethod = { * * Respond once the cancellation is accepted, then confirm it with a * `tool_call_update` to a final status. Throw `-32602` for a tool call - * that is unknown, already final, or not cancellable. Without this handler - * (and without a `"*"` request fallback), the app answers every + * that is unknown, already final, or not cancellable. This includes a + * request the Client sends again after the first one was accepted and the + * tool call has since reached a final status. Without this handler (and + * without a `"*"` request fallback), the app answers every * `tool_call/cancel` with `-32602`. */ [schema.AGENT_METHODS.tool_call_cancel]: AgentRequestHandler< @@ -2347,13 +2381,20 @@ export type AgentRequestHandlersByMethod = { * Respond once the cancellation is accepted, then confirm it with a * `terminal_update` that sets `exitStatus`. Cancelling does not release the * terminal. Throw `-32602` for a terminal that is unknown, has already - * exited, or is not cancellable. Without this handler (and without a `"*"` - * request fallback), the app answers every `terminal/cancel` with `-32602`. + * exited, or is not cancellable. This includes a request the Client sends + * again after the first one was accepted and the process has since exited. + * Without this handler (and without a `"*"` request fallback), the app + * answers every `terminal/cancel` with `-32602`. */ [schema.AGENT_METHODS.terminal_cancel]: AgentRequestHandler< schema.CancelTerminalRequest, schema.CancelTerminalResponse | void >; + /** + * Handles `auth/logout`. Logging out is idempotent: respond with the same + * empty result when there is no authenticated state to end, for example + * because the Client already logged out. + */ [schema.AGENT_METHODS.auth_logout]: AgentRequestHandler< schema.LogoutAuthRequest, schema.LogoutAuthResponse | void diff --git a/src/v2/unstable/schema/guards.gen.ts b/src/v2/unstable/schema/guards.gen.ts index 7839e0e4..d6cd7dc4 100644 --- a/src/v2/unstable/schema/guards.gen.ts +++ b/src/v2/unstable/schema/guards.gen.ts @@ -56,6 +56,9 @@ const zGuardContentBlockResourceLink = validate.zResourceLink const zGuardContentBlockResource = validate.zEmbeddedResource .and(z.object({ annotations: validate.zAnnotations.nullish() })) .and(z.object({ type: z.literal("resource") })); +const zGuardContentBlockSkill = validate.zSkillReference.and( + z.object({ type: z.literal("skill") }), +); const zGuardDiffChangeAdd = validate.zDiffPathChange.and( z.object({ operation: z.literal("add") }), ); @@ -120,6 +123,9 @@ const zGuardSessionConfigOptionCustom = z.object({ const zGuardAvailableCommandCommand = validate.zCommand .and(z.object({ input: validate.zAvailableCommandInput.nullish() })) .and(z.object({ type: z.literal("command") })); +const zGuardAvailableCommandSkill = validate.zSkill + .and(z.object({ path: validate.zAbsolutePath.nullish() })) + .and(z.object({ type: z.literal("skill") })); const zGuardAvailableCommandCustom = z.object({ name: z.string(), description: z.string(), @@ -245,6 +251,13 @@ const zGuardSessionUpdateCompactionSummaryChunk = validate.zCompactionSummaryChunk.and( z.object({ sessionUpdate: z.literal("compaction_summary_chunk") }), ); +const zGuardSessionUpdateReplayComplete = validate.zReplayComplete.and( + z.object({ sessionUpdate: z.literal("replay_complete") }), +); +const zGuardSessionUpdatePromptSuggestionsUpdate = + validate.zPromptSuggestionsUpdate.and( + z.object({ sessionUpdate: z.literal("prompt_suggestions_update") }), + ); const zGuardSessionUpdateSubagentUpdate = validate.zSubagentUpdate .and( z.object({ @@ -270,12 +283,9 @@ const zGuardSessionUpdateSessionMessageChunk = const zGuardPlanUpdateContentItems = validate.zPlanItems.and( z.object({ type: z.literal("items") }), ); -const zGuardPlanUpdateContentFile = validate.zPlanFile.and( - z.object({ type: z.literal("file") }), -); -const zGuardPlanUpdateContentMarkdown = validate.zPlanMarkdown.and( - z.object({ type: z.literal("markdown") }), -); +const zGuardPlanUpdateContentMarkdown = validate.zPlanMarkdown + .and(z.object({ path: validate.zAbsolutePath.nullish() })) + .and(z.object({ type: z.literal("markdown") })); const zGuardPlanUpdateContentCustom = z.object({ planId: validate.zPlanId }); const zGuardMcpServerHttp = validate.zMcpServerHttp.and( z.object({ type: z.literal("http") }), @@ -566,6 +576,16 @@ export const ContentBlock = { ); }, + /** Narrow to the `skill` variant, validating its payload. */ + isSkill( + value: types.ContentBlock, + ): value is types.SkillReference & { type: "skill" } { + return ( + tagOf(value, "type") === "skill" && + zGuardContentBlockSkill.safeParse(value).success + ); + }, + /** * Narrow to a custom or future variant: the `type` tag matches no known variant. * @@ -581,7 +601,14 @@ export const ContentBlock = { const tag = tagOf(value, "type"); return ( typeof tag === "string" && - !["audio", "image", "resource", "resource_link", "text"].includes(tag) + ![ + "audio", + "image", + "resource", + "resource_link", + "skill", + "text", + ].includes(tag) ); }, } as const; @@ -1089,6 +1116,16 @@ export const AvailableCommand = { ); }, + /** Narrow to the `skill` variant, validating its payload. */ + isSkill( + value: types.AvailableCommand, + ): value is types.Skill & { type: "skill" } { + return ( + tagOf(value, "type") === "skill" && + zGuardAvailableCommandSkill.safeParse(value).success + ); + }, + /** * Narrow to a custom or future variant: the `type` tag matches no known variant, with a valid payload. * @@ -1105,7 +1142,7 @@ export const AvailableCommand = { const tag = tagOf(value, "type"); return ( typeof tag === "string" && - !["command"].includes(tag) && + !["command", "skill"].includes(tag) && zGuardAvailableCommandCustom.safeParse(value).success ); }, @@ -1742,6 +1779,28 @@ export const SessionUpdate = { ); }, + /** Narrow to the `replay_complete` variant, validating its payload. */ + isReplayComplete( + value: types.SessionUpdate, + ): value is types.ReplayComplete & { sessionUpdate: "replay_complete" } { + return ( + tagOf(value, "sessionUpdate") === "replay_complete" && + zGuardSessionUpdateReplayComplete.safeParse(value).success + ); + }, + + /** Narrow to the `prompt_suggestions_update` variant, validating its payload. */ + isPromptSuggestionsUpdate( + value: types.SessionUpdate, + ): value is types.PromptSuggestionsUpdate & { + sessionUpdate: "prompt_suggestions_update"; + } { + return ( + tagOf(value, "sessionUpdate") === "prompt_suggestions_update" && + zGuardSessionUpdatePromptSuggestionsUpdate.safeParse(value).success + ); + }, + /** Narrow to the `subagent_update` variant, validating its payload. */ isSubagentUpdate( value: types.SessionUpdate, @@ -1801,6 +1860,8 @@ export const SessionUpdate = { "notice", "plan_removed", "plan_update", + "prompt_suggestions_update", + "replay_complete", "session_info_update", "session_message", "session_message_chunk", @@ -1848,16 +1909,6 @@ export const PlanUpdateContent = { ); }, - /** Narrow to the `file` variant, validating its payload. */ - isFile( - value: types.PlanUpdateContent, - ): value is types.PlanFile & { type: "file" } { - return ( - tagOf(value, "type") === "file" && - zGuardPlanUpdateContentFile.safeParse(value).success - ); - }, - /** Narrow to the `markdown` variant, validating its payload. */ isMarkdown( value: types.PlanUpdateContent, @@ -1884,7 +1935,7 @@ export const PlanUpdateContent = { const tag = tagOf(value, "type"); return ( typeof tag === "string" && - !["file", "items", "markdown"].includes(tag) && + !["items", "markdown"].includes(tag) && zGuardPlanUpdateContentCustom.safeParse(value).success ); }, diff --git a/src/v2/unstable/schema/index.ts b/src/v2/unstable/schema/index.ts index ad56b21d..777b1e0b 100644 --- a/src/v2/unstable/schema/index.ts +++ b/src/v2/unstable/schema/index.ts @@ -44,10 +44,13 @@ export type { ClientNotification, ClientRequest, ClientResponse, + ClientSessionCapabilities, CloseSessionRequest, CloseSessionResponse, Command, CommandPermissionSubject, + CommandsCapabilities, + CommandsSkillCapabilities, CompactionId, CompactionStatus, CompactionSummaryChunk, @@ -147,7 +150,6 @@ export type { PlanEntry, PlanEntryPriority, PlanEntryStatus, - PlanFile, PlanId, PlanItems, PlanMarkdown, @@ -160,6 +162,14 @@ export type { PromptImageCapabilities, PromptRequest, PromptResponse, + PromptSkillCapabilities, + PromptSkillInlineCapabilities, + PromptSuggestion, + PromptSuggestionsAudioCapabilities, + PromptSuggestionsCapabilities, + PromptSuggestionsEmbeddedContextCapabilities, + PromptSuggestionsImageCapabilities, + PromptSuggestionsUpdate, ProtocolVersion, ProviderCurrentConfig, ProviderId, @@ -171,6 +181,7 @@ export type { ProxySuccessorNotification, ProxySuccessorRequest, ProxySuccessorResponse, + ReplayComplete, ReplayFrom, ReplayFromStart, RequestId, @@ -228,6 +239,8 @@ export type { SetSessionConfigOptionResponse, SetSessionTitleRequest, SetSessionTitleResponse, + Skill, + SkillReference, StateUpdate, StringFormat, StringMultiSelectItems, diff --git a/src/v2/unstable/schema/types.gen.ts b/src/v2/unstable/schema/types.gen.ts index ee39e75a..7635a39d 100644 --- a/src/v2/unstable/schema/types.gen.ts +++ b/src/v2/unstable/schema/types.gen.ts @@ -357,6 +357,9 @@ export type ContentBlock = | (EmbeddedResource & { type: "resource"; }) + | (SkillReference & { + type: "skill"; + }) | ( | { /** @@ -722,6 +725,48 @@ export type EmbeddedResource = { } | null; }; +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * A reference to a skill that the user invokes with a prompt. + * + * The Client sends it in `session/prompt` to invoke a `skill` entry from the + * session's command list. The Agent MUST treat each reference as the user + * explicitly invoking that skill for this prompt, and applies the skill to + * the prompt as a whole. The reference carries no input. + * + * An Agent that advertises the `session.prompt.skill` capability accepts one + * skill reference per prompt, as the first content block. An Agent that also + * advertises `session.prompt.skill.inline` accepts any number of them, at any + * position. Clients MUST NOT send a skill reference that the Agent's + * capability does not allow. + * + * The `type` discriminator value is `skill`. + * + * @experimental + */ +export type SkillReference = { + /** + * The `name` of a `skill` entry in the session's current command list. + * + * Required and non-null. The Agent MUST reject the prompt with `-32602` + * when it cannot resolve the name to a skill the user can invoke. + */ + name: string; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * Standard content block (text, images, resources). */ @@ -944,10 +989,6 @@ export type Terminal = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * A display-only reference to another session that this tool call operates on, * such as a subagent it created, delegated to, or is waiting on. * @@ -955,8 +996,6 @@ export type Terminal = { * session setup or a `subagent_update` announcement. The reference does not * establish ownership, grant any Client-initiated controls, or report that the * referenced session received or finished any work. - * - * @experimental */ export type SessionReference = { /** @@ -2182,6 +2221,20 @@ export type PromptCapabilities = { * Supplying `{}` means the agent supports embedded context in prompts. */ embeddedContext?: PromptEmbeddedContextCapabilities | null; + /** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Agent supports `skill` content blocks. + * + * Optional. Omitted and `null` are equivalent and mean the agent does not advertise support. + * Supplying `{}` means a prompt may contain one `skill` content block, as its first block. + * An agent that sends `skill` entries in `availableCommands` MUST advertise this capability. + * + * @experimental + */ + skill?: PromptSkillCapabilities | null; /** * The _meta property is reserved by ACP to allow clients and agents to attach additional * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. @@ -2248,6 +2301,64 @@ export type PromptEmbeddedContextCapabilities = { } | null; }; +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for `skill` content blocks in prompt requests. + * + * Supplying `{}` means a prompt may contain one `skill` content block, as + * its first block. The agent opts in to more with the `inline` field. + * + * @experimental + */ +export type PromptSkillCapabilities = { + /** + * Agent supports `skill` content blocks at any position in a prompt. + * + * Optional. Omitted and `null` are equivalent and mean the agent does not advertise support. + * Supplying `{}` means a prompt may contain any number of `skill` content blocks, at any + * position. + */ + inline?: PromptSkillInlineCapabilities | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for `skill` content blocks at any position in a prompt. + * + * Supplying `{}` means a prompt may contain any number of `skill` content + * blocks, at any position. + * + * @experimental + */ +export type PromptSkillInlineCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * MCP capabilities supported by the agent for session lifecycle requests. */ @@ -3267,7 +3378,7 @@ export type SessionConfigId = string; * Category names that do not begin with `_` are reserved for the ACP spec. */ export type SessionConfigOptionCategory = - "mode" | "model" | "model_config" | "thought_level" | string; + "mode" | "model" | "model_config" | "speed" | "thought_level" | string; /** * Unique identifier for a session configuration option value. @@ -3377,6 +3488,9 @@ export type AvailableCommand = | (Command & { type: "command"; }) + | (Skill & { + type: "skill"; + }) | ( | { /** @@ -3388,8 +3502,7 @@ export type AvailableCommand = */ type: `_${string}`; /** - * Command name without the leading `/`. MUST NOT contain whitespace and MUST be unique - * within the command list. + * Command name. MUST NOT contain whitespace and MUST be unique within the command list. */ name: string; /** @@ -3408,8 +3521,7 @@ export type AvailableCommand = */ type: string; /** - * Command name without the leading `/`. MUST NOT contain whitespace and MUST be unique - * within the command list. + * Command name. MUST NOT contain whitespace and MUST be unique within the command list. */ name: string; /** @@ -3506,6 +3618,58 @@ export type Command = { } | null; }; +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * An Agent Skill the user can invoke. + * + * The `type` discriminator value is `skill`. + * + * The Client invokes a skill with a `skill` content block that names it. The + * `/name` syntax applies only to `command` entries. An Agent that sends + * `skill` entries MUST advertise the `session.prompt.skill` capability. + * + * Agents MUST only send `skill` entries when the Client advertised the + * `session.commands.skill` Client capability. Agents SHOULD advertise only + * skills the user can invoke. + * + * @experimental + */ +export type Skill = { + /** + * Skill name. The Client uses it to refer to the skill in a `skill` content block. + * + * MUST NOT contain whitespace and MUST be unique within the command list, + * across commands and skills. It need not match the `name` in the skill's + * frontmatter. + */ + name: string; + /** + * Human-readable description of what the skill does, shown to users. + */ + description: string; + /** + * The absolute path of the file that defines the skill, normally its + * `SKILL.md`, on the machine the Agent runs on. + * + * Omitted and `null` are equivalent and mean the skill has no such file, + * as with a skill that is built into the Agent or served by an MCP server. + */ + path?: AbsolutePath | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * Response from listing sessions. */ @@ -3914,13 +4078,7 @@ export type BackgroundToolCall = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * A listed subagent session associated with this session through `subagent_update`. - * - * @experimental */ export type BackgroundSubagent = { /** @@ -4271,10 +4429,6 @@ export type RequiresActionStateUpdate = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * The Agent cannot currently determine foreground activity. * * Report this when activity becomes unobservable, not merely because the session @@ -4283,9 +4437,10 @@ export type RequiresActionStateUpdate = { * snapshot normally. * * This is not a task outcome or session closure. It does not cancel work, resolve - * pending requests, or revoke capabilities. Capabilities are updated separately. - * - * @experimental + * pending requests, or revoke capabilities. If a control is no longer available, + * the Agent updates the capabilities explicitly. Unfinished tool calls and + * terminals stay as last reported. The Agent does not invent a final status + * for them, and Clients do not present them as confirmed live activity. */ export type UnknownStateUpdate = { /** @@ -4658,6 +4813,12 @@ export type SessionUpdate = | (CompactionSummaryChunk & { sessionUpdate: "compaction_summary_chunk"; }) + | (ReplayComplete & { + sessionUpdate: "replay_complete"; + }) + | (PromptSuggestionsUpdate & { + sessionUpdate: "prompt_suggestions_update"; + }) | (SubagentUpdate & { sessionUpdate: "subagent_update"; }) @@ -5080,9 +5241,6 @@ export type PlanUpdateContent = | (PlanItems & { type: "items"; }) - | (PlanFile & { - type: "file"; - }) | (PlanMarkdown & { type: "markdown"; }) @@ -5202,36 +5360,6 @@ export type PlanItems = { } | null; }; -/** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A plan represented by a file URI. - * - * @experimental - */ -export type PlanFile = { - /** - * The plan ID to update. - */ - planId: PlanId; - /** - * The URI of the file containing the plan. - */ - uri: string; - /** - * The _meta property is reserved by ACP to allow clients and agents to attach additional - * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. - * Implementations MUST NOT assign meaning to keys they do not recognize. - * - * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) - */ - _meta?: { - [key: string]: unknown; - } | null; -}; - /** * **UNSTABLE** * @@ -5250,6 +5378,13 @@ export type PlanMarkdown = { * Markdown content for the plan. */ content: string; + /** + * The absolute path of the file that holds this plan. + * + * Omitted and `null` are equivalent and mean the plan has no file. `content` is the + * plan to display whether or not `path` is set. Clients can use `path` to open the file. + */ + path?: AbsolutePath | null; /** * The _meta property is reserved by ACP to allow clients and agents to attach additional * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. @@ -5556,17 +5691,116 @@ export type CompactionSummaryChunk = { } | null; }; +/** + * Marks the end of the history replay requested by `session/resume`. + * + * The Agent sends this once on a session for each `session/resume` request + * with a `replayFrom` cursor, after the last replayed update for that session + * and before the response. It sends it for an empty history too. Every update + * for the session between the request and this update is replayed history. + * Every update after it is not. + * + * The Agent does not send it when `replayFrom` is omitted or `null`, or when + * it cannot finish the replay. A resume that ends without it delivered a + * truncated replay. + * + * See protocol docs: [Replay Complete](https://agentclientprotocol.com/protocol/v2/draft/session-setup#replay-complete) + */ +export type ReplayComplete = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * A prompt the Agent predicts the user will send next. + * + * @experimental + */ +export type PromptSuggestion = { + /** + * The content of the suggested prompt. + * + * Required and non-null. Must contain at least one content block. This is + * the same type as the `prompt` field of `session/prompt`, and the Client + * can send it in that field unchanged. + * + * Agents MUST only use `text` and `resource_link` content blocks, and + * other content block types that both the Agent's `session.prompt` + * capability and the Client's `session.promptSuggestions` capability + * advertise. Agents MUST NOT send a prompt whose only content is empty + * or whitespace-only text. + */ + prompt: Array; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * **UNSTABLE** * * This capability is not part of the spec yet, and may be removed or changed at any point. * + * Replaces the session's current list of prompt suggestions. + * + * A prompt suggestion is a prompt the Agent predicts the user will send next. + * The Client offers suggestions where the user writes the next prompt. The + * list in the most recent update is the current list until the Agent sends + * another update. A session starts with an empty list. + * + * Suggestions are not part of session history. Agents SHOULD NOT include + * this update when they replay a session. + * + * Agents MUST only send this update when the Client advertised + * the `session.promptSuggestions` Client capability. + * + * @experimental + */ +export type PromptSuggestionsUpdate = { + /** + * The suggestions the Client can offer, ordered most likely first. + * + * Required and non-null. An empty list withdraws the suggestions the + * Client is offering. A Client skips an entry it cannot parse and keeps + * the rest. + */ + suggestions: Array; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** * Client-initiated session mutations permitted for a specific subagent session. * * A mutation requires an explicit per-child capability. Support for the method * on ordinary sessions does not grant support on a child. - * - * @experimental */ export type SubagentSessionCapabilities = { /** @@ -5588,15 +5822,9 @@ export type SubagentSessionCapabilities = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Capability to cancel work in a subagent session without ending that session. * * Supplying `{}` advertises support. An omitted or `null` `cancel` does not. - * - * @experimental */ export type SessionCancelCapabilities = { /** @@ -5612,10 +5840,6 @@ export type SessionCancelCapabilities = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Notification that the enclosing parent session created and owns a child session. * * Later updates modify the existing association's metadata, not its ownership. @@ -5648,8 +5872,6 @@ export type SessionCancelCapabilities = { * Completing or cancelling work does not end the association: the parent may * message the same child again. Individual messages and their outcomes do not * change this association. - * - * @experimental */ export type SubagentUpdate = { /** @@ -5718,13 +5940,7 @@ export type SubagentUpdate = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * An upsert for an inter-session message. - * - * @experimental */ export type SessionMessage = { /** @@ -5780,16 +5996,10 @@ export type SessionMessage = { }; /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * A streamed content block of an inter-session message. * * A chunk carries only content. The message's participants and references come from * `session_message` upserts, which can arrive before, between, or after its chunks. - * - * @experimental */ export type SessionMessageChunk = { /** @@ -5985,6 +6195,20 @@ export type ClientCapabilities = { * elicitation support. */ elicitation?: ElicitationCapabilities | null; + /** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Session-related capabilities supported by the client. + * + * Optional. Omitted and `null` are equivalent and mean the client does not advertise any + * session-related capabilities. This object only groups capabilities. Supplying `{}` + * advertises nothing. + * + * @experimental + */ + session?: ClientSessionCapabilities | null; /** * The _meta property is reserved by ACP to allow clients and agents to attach additional * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. @@ -6112,6 +6336,228 @@ export type ElicitationUrlCapabilities = { } | null; }; +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Session-related capabilities supported by the client. + * + * This object only groups capabilities. Unlike the agent's `session` + * capability, it is not a support marker, and supplying `{}` advertises + * nothing. + * + * @experimental + */ +export type ClientSessionCapabilities = { + /** + * Support for `prompt_suggestions_update` session updates. + * + * Optional. Omitted and `null` are equivalent and mean the client does not advertise support. + * Supplying `{}` means the client can offer prompt suggestions made of + * `text` and `resource_link` content blocks. + */ + promptSuggestions?: PromptSuggestionsCapabilities | null; + /** + * Command types the client understands in addition to `command`. + * + * Optional. Omitted and `null` are equivalent and mean the client does not advertise support + * for any of them. This object only groups capabilities. Supplying `{}` advertises nothing. + */ + commands?: CommandsCapabilities | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Client support for `prompt_suggestions_update` session updates. + * + * Supplying `{}` means the client can offer prompt suggestions made of `text` + * and `resource_link` content blocks. The client opts in to other content + * block types with the fields below. They have the same names as the agent's + * `session.prompt` capability. + * + * @experimental + */ +export type PromptSuggestionsCapabilities = { + /** + * Whether prompt suggestions may include `image` content blocks. + * + * Optional. Omitted and `null` are equivalent and mean the client does not advertise support. + * Supplying `{}` means the client can offer suggestions that include images. + */ + image?: PromptSuggestionsImageCapabilities | null; + /** + * Whether prompt suggestions may include `audio` content blocks. + * + * Optional. Omitted and `null` are equivalent and mean the client does not advertise support. + * Supplying `{}` means the client can offer suggestions that include audio. + */ + audio?: PromptSuggestionsAudioCapabilities | null; + /** + * Whether prompt suggestions may include `resource` content blocks. + * + * Optional. Omitted and `null` are equivalent and mean the client does not advertise support. + * Supplying `{}` means the client can offer suggestions that include + * embedded resources. + */ + embeddedContext?: PromptSuggestionsEmbeddedContextCapabilities | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for image content in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include images. + * + * @experimental + */ +export type PromptSuggestionsImageCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for audio content in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include audio. + * + * @experimental + */ +export type PromptSuggestionsAudioCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for embedded resources in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include + * `resource` content blocks. + * + * @experimental + */ +export type PromptSuggestionsEmbeddedContextCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Command types the client understands in addition to `command`. + * + * Agents MUST NOT send a command type listed here unless the client + * advertised it. This object only groups capabilities. Supplying `{}` + * advertises nothing. + * + * @experimental + */ +export type CommandsCapabilities = { + /** + * Support for `skill` command entries. + * + * Optional. Omitted and `null` are equivalent and mean the client does not advertise support. + * Supplying `{}` means the agent may send `skill` entries in `availableCommands`. The + * client invokes them with `skill` content blocks. + */ + skill?: CommandsSkillCapabilities | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Client support for `skill` command entries. + * + * Supplying `{}` means the agent may send `skill` entries in + * `availableCommands`. The client invokes them with `skill` content blocks. + * + * @experimental + */ +export type CommandsSkillCapabilities = { + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. + * Implementations MUST NOT assign meaning to keys they do not recognize. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; +}; + /** * Request parameters for the `auth/login` method. * @@ -6324,7 +6770,9 @@ export type NewSessionRequest = { * entries and duplicate `configId` values fail the request with `-32602`. The Agent MUST * ignore an entry whose `configId` is unknown, whose `type` does not match the option, * or whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually - * has. The Agent SHOULD NOT send `config_option_update` notifications for these entries. + * has. The Agent SHOULD NOT send `config_option_update` notifications for these entries, + * and SHOULD NOT send other `session/update` notifications caused by applying them + * before it responds. * * @experimental */ @@ -6758,6 +7206,29 @@ export type ForkSessionRequest = { * source session. */ systemPrompt?: SystemPrompt | null; + /** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Initial values for the forked session's config options. + * + * Optional. Omitted and `null` are equivalent to an empty list. Clients MUST only send + * this when the Agent advertises `session.options`. + * + * The Agent applies the entries in list order on top of the configuration the fork + * starts with, as if the Client had called `session/set_config_option` for each entry + * after the session was set up. Malformed entries and duplicate `configId` values fail + * the request with `-32602`. The Agent MUST ignore an entry whose `configId` is unknown, + * whose `type` does not match the option, or whose value is not allowed when it is + * applied. The `configOptions` in the response contain the values the session actually + * has. The Agent SHOULD NOT send `config_option_update` notifications for these entries, + * and SHOULD NOT send other `session/update` notifications caused by applying them + * before it responds. + * + * @experimental + */ + configOptions?: Array; /** * The _meta property is reserved by ACP to allow clients and agents to attach additional * metadata to their interactions. Omitted and `null` are equivalent and mean no metadata. @@ -6881,7 +7352,8 @@ export type SessionStatusRequest = { * * `session/options` MUST NOT create or resume a session, send `session/update` * notifications, add an entry to `session/list`, or change an existing session. Agents - * SHOULD NOT connect to MCP servers to answer it. + * SHOULD NOT connect to MCP servers to answer it. Agents MAY start their runtime to resolve + * the options. The rules above still hold for it. * * @experimental */ @@ -7033,7 +7505,9 @@ export type ResumeSessionRequest = { * entries and duplicate `configId` values fail the request with `-32602`. The Agent MUST * ignore an entry whose `configId` is unknown, whose `type` does not match the option, * or whose value is not allowed when it is applied. The `configOptions` in the response contain the values the session actually - * has. The Agent SHOULD NOT send `config_option_update` notifications for these entries. + * has. The Agent SHOULD NOT send `config_option_update` notifications for these entries + * on the requesting connection. If the session is active on other connections, the + * Agent SHOULD send `config_option_update` on those connections. * * The Agent applies the entries before it replays history for `replayFrom` and before * it responds. diff --git a/src/v2/unstable/schema/zod.gen.ts b/src/v2/unstable/schema/zod.gen.ts index 9ddcd217..80bf0cd0 100644 --- a/src/v2/unstable/schema/zod.gen.ts +++ b/src/v2/unstable/schema/zod.gen.ts @@ -229,6 +229,36 @@ export const zEmbeddedResource = z.object({ ), }); +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * A reference to a skill that the user invokes with a prompt. + * + * The Client sends it in `session/prompt` to invoke a `skill` entry from the + * session's command list. The Agent MUST treat each reference as the user + * explicitly invoking that skill for this prompt, and applies the skill to + * the prompt as a whole. The reference carries no input. + * + * An Agent that advertises the `session.prompt.skill` capability accepts one + * skill reference per prompt, as the first content block. An Agent that also + * advertises `session.prompt.skill.inline` accepts any number of them, at any + * position. Clients MUST NOT send a skill reference that the Agent's + * capability does not allow. + * + * The `type` discriminator value is `skill`. + * + * @experimental + */ +export const zSkillReference = z.object({ + name: z.string(), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * Content blocks represent displayable information in the Agent Client Protocol. * @@ -274,16 +304,21 @@ export const zContentBlock = preserveCustomPayload( type: z.literal("resource"), }), ), + zSkillReference.and( + z.object({ + type: z.literal("skill"), + }), + ), excludeKnownTags( z.object({ type: z.string(), }), "type", - ["audio", "image", "resource", "resource_link", "text"], + ["audio", "image", "resource", "resource_link", "skill", "text"], ), ]), "type", - ["audio", "image", "resource", "resource_link", "text"], + ["audio", "image", "resource", "resource_link", "skill", "text"], ); /** @@ -446,10 +481,6 @@ export const zTerminal = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * A display-only reference to another session that this tool call operates on, * such as a subagent it created, delegated to, or is waiting on. * @@ -457,8 +488,6 @@ export const zTerminal = z.object({ * session setup or a `subagent_update` announcement. The reference does not * establish ownership, grant any Client-initiated controls, or report that the * referenced session received or finished any work. - * - * @experimental */ export const zSessionReference = z.object({ sessionId: zSessionId, @@ -1199,6 +1228,48 @@ export const zPromptEmbeddedContextCapabilities = z.object({ ), }); +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for `skill` content blocks at any position in a prompt. + * + * Supplying `{}` means a prompt may contain any number of `skill` content + * blocks, at any position. + * + * @experimental + */ +export const zPromptSkillInlineCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for `skill` content blocks in prompt requests. + * + * Supplying `{}` means a prompt may contain one `skill` content block, as + * its first block. The agent opts in to more with the `inline` field. + * + * @experimental + */ +export const zPromptSkillCapabilities = z.object({ + inline: defaultOnError( + zPromptSkillInlineCapabilities.nullish(), + () => undefined, + ), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * Prompt capabilities supported by the agent in `session/prompt` requests. * @@ -1220,6 +1291,7 @@ export const zPromptCapabilities = z.object({ zPromptEmbeddedContextCapabilities.nullish(), () => undefined, ), + skill: defaultOnError(zPromptSkillCapabilities.nullish(), () => undefined), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -1891,6 +1963,7 @@ export const zSessionConfigOptionCategory = z.union([ z.literal("mode"), z.literal("model"), z.literal("model_config"), + z.literal("speed"), z.literal("thought_level"), z.string(), ]); @@ -2055,6 +2128,35 @@ export const zCommand = z.object({ ), }); +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * An Agent Skill the user can invoke. + * + * The `type` discriminator value is `skill`. + * + * The Client invokes a skill with a `skill` content block that names it. The + * `/name` syntax applies only to `command` entries. An Agent that sends + * `skill` entries MUST advertise the `session.prompt.skill` capability. + * + * Agents MUST only send `skill` entries when the Client advertised the + * `session.commands.skill` Client capability. Agents SHOULD advertise only + * skills the user can invoke. + * + * @experimental + */ +export const zSkill = z.object({ + name: z.string(), + description: z.string(), + path: defaultOnError(zAbsolutePath.nullish(), () => undefined), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * A command the agent advertises to the client. * @@ -2071,6 +2173,11 @@ export const zAvailableCommand = preserveCustomPayload( type: z.literal("command"), }), ), + zSkill.and( + z.object({ + type: z.literal("skill"), + }), + ), excludeKnownTags( z.object({ type: z.string(), @@ -2078,11 +2185,11 @@ export const zAvailableCommand = preserveCustomPayload( description: z.string(), }), "type", - ["command"], + ["command", "skill"], ), ]), "type", - ["command"], + ["command", "skill"], ); /** @@ -2254,13 +2361,7 @@ export const zBackgroundToolCall = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * A listed subagent session associated with this session through `subagent_update`. - * - * @experimental */ export const zBackgroundSubagent = z.object({ sessionId: zSessionId, @@ -2541,10 +2642,6 @@ export const zRequiresActionStateUpdate = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * The Agent cannot currently determine foreground activity. * * Report this when activity becomes unobservable, not merely because the session @@ -2553,9 +2650,10 @@ export const zRequiresActionStateUpdate = z.object({ * snapshot normally. * * This is not a task outcome or session closure. It does not cancel work, resolve - * pending requests, or revoke capabilities. Capabilities are updated separately. - * - * @experimental + * pending requests, or revoke capabilities. If a control is no longer available, + * the Agent updates the capabilities explicitly. Unfinished tool calls and + * terminals stay as last reported. The Agent does not invent a final status + * for them, and Clients do not present them as confirmed live activity. */ export const zUnknownStateUpdate = z.object({ background: defaultOnError( @@ -3133,24 +3231,6 @@ export const zPlanItems = z.object({ ), }); -/** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * - * A plan represented by a file URI. - * - * @experimental - */ -export const zPlanFile = z.object({ - planId: zPlanId, - uri: z.url(), - _meta: defaultOnError( - z.record(z.string(), z.unknown()).nullish(), - () => undefined, - ), -}); - /** * **UNSTABLE** * @@ -3163,6 +3243,7 @@ export const zPlanFile = z.object({ export const zPlanMarkdown = z.object({ planId: zPlanId, content: z.string(), + path: defaultOnError(zAbsolutePath.nullish(), () => undefined), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -3183,11 +3264,6 @@ export const zPlanUpdateContent = preserveCustomPayload( type: z.literal("items"), }), ), - zPlanFile.and( - z.object({ - type: z.literal("file"), - }), - ), zPlanMarkdown.and( z.object({ type: z.literal("markdown"), @@ -3199,11 +3275,11 @@ export const zPlanUpdateContent = preserveCustomPayload( planId: zPlanId, }), "type", - ["file", "items", "markdown"], + ["items", "markdown"], ), ]), "type", - ["file", "items", "markdown"], + ["items", "markdown"], ); /** @@ -3384,18 +3460,39 @@ export const zCompactionSummaryChunk = z.object({ ), }); +/** + * Marks the end of the history replay requested by `session/resume`. + * + * The Agent sends this once on a session for each `session/resume` request + * with a `replayFrom` cursor, after the last replayed update for that session + * and before the response. It sends it for an empty history too. Every update + * for the session between the request and this update is replayed history. + * Every update after it is not. + * + * The Agent does not send it when `replayFrom` is omitted or `null`, or when + * it cannot finish the replay. A resume that ends without it delivered a + * truncated replay. + * + * See protocol docs: [Replay Complete](https://agentclientprotocol.com/protocol/v2/draft/session-setup#replay-complete) + */ +export const zReplayComplete = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * **UNSTABLE** * * This capability is not part of the spec yet, and may be removed or changed at any point. * - * Capability to cancel work in a subagent session without ending that session. - * - * Supplying `{}` advertises support. An omitted or `null` `cancel` does not. + * A prompt the Agent predicts the user will send next. * * @experimental */ -export const zSessionCancelCapabilities = z.object({ +export const zPromptSuggestion = z.object({ + prompt: z.array(zContentBlock).min(1), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -3407,12 +3504,46 @@ export const zSessionCancelCapabilities = z.object({ * * This capability is not part of the spec yet, and may be removed or changed at any point. * + * Replaces the session's current list of prompt suggestions. + * + * A prompt suggestion is a prompt the Agent predicts the user will send next. + * The Client offers suggestions where the user writes the next prompt. The + * list in the most recent update is the current list until the Agent sends + * another update. A session starts with an empty list. + * + * Suggestions are not part of session history. Agents SHOULD NOT include + * this update when they replay a session. + * + * Agents MUST only send this update when the Client advertised + * the `session.promptSuggestions` Client capability. + * + * @experimental + */ +export const zPromptSuggestionsUpdate = z.object({ + suggestions: vecSkipError(zPromptSuggestion), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * Capability to cancel work in a subagent session without ending that session. + * + * Supplying `{}` advertises support. An omitted or `null` `cancel` does not. + */ +export const zSessionCancelCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** * Client-initiated session mutations permitted for a specific subagent session. * * A mutation requires an explicit per-child capability. Support for the method * on ordinary sessions does not grant support on a child. - * - * @experimental */ export const zSubagentSessionCapabilities = z.object({ cancel: defaultOnError(zSessionCancelCapabilities.nullish(), () => undefined), @@ -3423,10 +3554,6 @@ export const zSubagentSessionCapabilities = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * Notification that the enclosing parent session created and owns a child session. * * Later updates modify the existing association's metadata, not its ownership. @@ -3459,8 +3586,6 @@ export const zSubagentSessionCapabilities = z.object({ * Completing or cancelling work does not end the association: the parent may * message the same child again. Individual messages and their outcomes do not * change this association. - * - * @experimental */ export const zSubagentUpdate = z.object({ sessionId: zSessionId, @@ -3480,13 +3605,7 @@ export const zSubagentUpdate = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * An upsert for an inter-session message. - * - * @experimental */ export const zSessionMessage = z.object({ messageId: zMessageId, @@ -3505,16 +3624,10 @@ export const zSessionMessage = z.object({ }); /** - * **UNSTABLE** - * - * This capability is not part of the spec yet, and may be removed or changed at any point. - * * A streamed content block of an inter-session message. * * A chunk carries only content. The message's participants and references come from * `session_message` upserts, which can arrive before, between, or after its chunks. - * - * @experimental */ export const zSessionMessageChunk = z.object({ messageId: zMessageId, @@ -3638,6 +3751,16 @@ export const zSessionUpdate = preserveCustomPayload( sessionUpdate: z.literal("compaction_summary_chunk"), }), ), + zReplayComplete.and( + z.object({ + sessionUpdate: z.literal("replay_complete"), + }), + ), + zPromptSuggestionsUpdate.and( + z.object({ + sessionUpdate: z.literal("prompt_suggestions_update"), + }), + ), zSubagentUpdate.and( z.object({ sessionUpdate: z.literal("subagent_update"), @@ -3670,6 +3793,8 @@ export const zSessionUpdate = preserveCustomPayload( "notice", "plan_removed", "plan_update", + "prompt_suggestions_update", + "replay_complete", "session_info_update", "session_message", "session_message_chunk", @@ -3698,6 +3823,8 @@ export const zSessionUpdate = preserveCustomPayload( "notice", "plan_removed", "plan_update", + "prompt_suggestions_update", + "replay_complete", "session_info_update", "session_message", "session_message_chunk", @@ -3856,6 +3983,159 @@ export const zElicitationCapabilities = z.object({ ), }); +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for image content in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include images. + * + * @experimental + */ +export const zPromptSuggestionsImageCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for audio content in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include audio. + * + * @experimental + */ +export const zPromptSuggestionsAudioCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Capabilities for embedded resources in prompt suggestions. + * + * Supplying `{}` means the client can offer suggestions that include + * `resource` content blocks. + * + * @experimental + */ +export const zPromptSuggestionsEmbeddedContextCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Client support for `prompt_suggestions_update` session updates. + * + * Supplying `{}` means the client can offer prompt suggestions made of `text` + * and `resource_link` content blocks. The client opts in to other content + * block types with the fields below. They have the same names as the agent's + * `session.prompt` capability. + * + * @experimental + */ +export const zPromptSuggestionsCapabilities = z.object({ + image: defaultOnError( + zPromptSuggestionsImageCapabilities.nullish(), + () => undefined, + ), + audio: defaultOnError( + zPromptSuggestionsAudioCapabilities.nullish(), + () => undefined, + ), + embeddedContext: defaultOnError( + zPromptSuggestionsEmbeddedContextCapabilities.nullish(), + () => undefined, + ), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Client support for `skill` command entries. + * + * Supplying `{}` means the agent may send `skill` entries in + * `availableCommands`. The client invokes them with `skill` content blocks. + * + * @experimental + */ +export const zCommandsSkillCapabilities = z.object({ + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Command types the client understands in addition to `command`. + * + * Agents MUST NOT send a command type listed here unless the client + * advertised it. This object only groups capabilities. Supplying `{}` + * advertises nothing. + * + * @experimental + */ +export const zCommandsCapabilities = z.object({ + skill: defaultOnError(zCommandsSkillCapabilities.nullish(), () => undefined), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + +/** + * **UNSTABLE** + * + * This capability is not part of the spec yet, and may be removed or changed at any point. + * + * Session-related capabilities supported by the client. + * + * This object only groups capabilities. Unlike the agent's `session` + * capability, it is not a support marker, and supplying `{}` advertises + * nothing. + * + * @experimental + */ +export const zClientSessionCapabilities = z.object({ + promptSuggestions: defaultOnError( + zPromptSuggestionsCapabilities.nullish(), + () => undefined, + ), + commands: defaultOnError(zCommandsCapabilities.nullish(), () => undefined), + _meta: defaultOnError( + z.record(z.string(), z.unknown()).nullish(), + () => undefined, + ), +}); + /** * Capabilities supported by the client. * @@ -3870,6 +4150,10 @@ export const zClientCapabilities = z.object({ zElicitationCapabilities.nullish(), () => undefined, ), + session: defaultOnError( + zClientSessionCapabilities.nullish(), + () => undefined, + ), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -4276,6 +4560,10 @@ export const zForkSessionRequest = z.object({ ).optional(), mcpServers: defaultOnNull(z.array(zMcpServer), () => []).optional(), systemPrompt: zSystemPrompt.nullish(), + configOptions: defaultOnNull( + z.array(zInitialSessionConfigOption), + () => [], + ).optional(), _meta: defaultOnError( z.record(z.string(), z.unknown()).nullish(), () => undefined, @@ -4366,7 +4654,8 @@ export const zSessionStatusRequest = z.object({ * * `session/options` MUST NOT create or resume a session, send `session/update` * notifications, add an entry to `session/list`, or change an existing session. Agents - * SHOULD NOT connect to MCP servers to answer it. + * SHOULD NOT connect to MCP servers to answer it. Agents MAY start their runtime to resolve + * the options. The rules above still hold for it. * * @experimental */