diff --git a/docs/protocol/draft/schema.mdx b/docs/protocol/draft/schema.mdx
index ebc6338c..d36d832b 100644
--- a/docs/protocol/draft/schema.mdx
+++ b/docs/protocol/draft/schema.mdx
@@ -1306,6 +1306,25 @@ Request parameters for setting a session configuration option.
**Type:** Union
+**Shared properties:**
+
+
+ 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/extensibility)
+
+
+SessionConfigId} required>
+ The ID of the configuration option to set.
+
+SessionId} required>
+ The ID of the session to set the configuration option for.
+
+
+**Variants:**
+
A boolean value (`type: "boolean"`).
@@ -1483,21 +1502,22 @@ Clients are typically code editors (IDEs, text editors) that provide the interfa
between users and AI agents. They manage the environment, handle user interactions,
and control access to resources.
-
-### fs/read_text_file
+
+### elicitation/complete
-Reads content from a text file in the client's file system.
+**UNSTABLE**
-Only available if the client advertises the `fs.readTextFile` capability.
-Allows the agent to access file contents within the client's environment.
+This capability is not part of the spec yet, and may be removed or changed at any point.
-See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
+Notification that a URL-based elicitation has completed.
-#### ReadTextFileRequest
+#### CompleteElicitationNotification
-Request to read content from a text file.
+**UNSTABLE**
-Only available if the client supports the `fs.readTextFile` capability.
+This capability is not part of the spec yet, and may be removed or changed at any point.
+
+Notification sent by the agent when a URL-based elicitation is complete.
**Type:** Object
@@ -1511,63 +1531,35 @@ these keys.
See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
-
- Maximum number of lines to read.
-
- - Minimum: `0`
-
-
-
- Line number to start reading from (1-based).
-
- - Minimum: `0`
-
-
-
- Absolute path to the file to read.
-
-SessionId} required>
- The session ID for this request.
+ElicitationId} required>
+ The ID of the elicitation that completed.
-#### ReadTextFileResponse
-
-Response containing the contents of a text file.
-
-**Type:** Object
-
-**Properties:**
-
-
- 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.
+
+### elicitation/create
-See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
-
-
-
-
+**UNSTABLE**
-
-### fs/write_text_file
+This capability is not part of the spec yet, and may be removed or changed at any point.
-Writes content to a text file in the client's file system.
+Requests structured user input via a form or URL.
-Only available if the client advertises the `fs.writeTextFile` capability.
-Allows the agent to create or modify files within the client's environment.
+#### CreateElicitationRequest
-See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
+**UNSTABLE**
-#### WriteTextFileRequest
+This capability is not part of the spec yet, and may be removed or changed at any point.
-Request to write content to a text file.
+Request from the agent to elicit structured user input.
-Only available if the client supports the `fs.writeTextFile` capability.
+The agent sends this to the client to request information from the user,
+either via a form or by directing them to a URL.
+Elicitations may be tied to a session, request, or tool call,
+or sent without a specific scope.
-**Type:** Object
+**Type:** Union
-**Properties:**
+**Shared properties:**
The _meta property is reserved by ACP to allow clients and agents to attach additional
@@ -1577,54 +1569,21 @@ these keys.
See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
-
- The text content to write to the file.
+
+ A human-readable message describing what input is needed.
-
- Absolute path to the file to write.
+RequestId | null>} >
+ Optional request ID if this elicitation is tied to a specific request.
-SessionId} required>
- The session ID for this request.
+SessionId | null>} >
+ Optional session ID if this elicitation is tied to a specific session.
-
-#### WriteTextFileResponse
-
-Response to `fs/write_text_file`
-
-**Type:** Object
-
-**Properties:**
-
-
- 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/extensibility)
-
+ToolCallId | null>} >
+ Optional tool call ID if this elicitation is tied to a specific tool call.
+When present, `sessionId` should also be set.
-
-### session/elicitation
-
-**UNSTABLE**
-
-This capability is not part of the spec yet, and may be removed or changed at any point.
-
-Requests structured user input via a form or URL.
-
-#### ElicitationRequest
-
-**UNSTABLE**
-
-This capability is not part of the spec yet, and may be removed or changed at any point.
-
-Request from the agent to elicit structured user input.
-
-The agent sends this to the client to request information from the user,
-either via a form or by directing them to a URL.
-
-**Type:** Union
+**Variants:**
Form-based elicitation where the client renders a form from the provided schema.
@@ -1667,7 +1626,7 @@ URL-based elicitation where the client directs the user to a URL.
-#### ElicitationResponse
+#### CreateElicitationResponse
**UNSTABLE**
@@ -1675,6 +1634,76 @@ This capability is not part of the spec yet, and may be removed or changed at an
Response from the client to an elicitation request.
+**Type:** Union
+
+**Shared properties:**
+
+
+ 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/extensibility)
+
+
+
+**Variants:**
+
+
+The user accepted and provided content.
+
+
+
+
+ The discriminator value. Must be `"accept"`.
+
+
+ The user-provided content, if any, as an object matching the requested schema.
+
+
+
+
+
+
+The user declined the elicitation.
+
+
+
+
+ The discriminator value. Must be `"decline"`.
+
+
+
+
+
+
+The elicitation was cancelled.
+
+
+
+
+ The discriminator value. Must be `"cancel"`.
+
+
+
+
+
+
+### fs/read_text_file
+
+Reads content from a text file in the client's file system.
+
+Only available if the client advertises the `fs.readTextFile` capability.
+Allows the agent to access file contents within the client's environment.
+
+See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
+
+#### ReadTextFileRequest
+
+Request to read content from a text file.
+
+Only available if the client supports the `fs.readTextFile` capability.
+
**Type:** Object
**Properties:**
@@ -1687,26 +1716,59 @@ these keys.
See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
-ElicitationAction} required>
- The user's action in response to the elicitation.
+
+ Maximum number of lines to read.
+
+ - Minimum: `0`
+
+
+ Line number to start reading from (1-based).
-
-### session/elicitation/complete
+ - Minimum: `0`
-**UNSTABLE**
+
+
+ Absolute path to the file to read.
+
+SessionId} required>
+ The session ID for this request.
+
-This capability is not part of the spec yet, and may be removed or changed at any point.
+#### ReadTextFileResponse
-Notification that a URL-based elicitation has completed.
+Response containing the contents of a text file.
-#### ElicitationCompleteNotification
+**Type:** Object
-**UNSTABLE**
+**Properties:**
-This capability is not part of the spec yet, and may be removed or changed at any point.
+
+ 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.
-Notification sent by the agent when a URL-based elicitation is complete.
+See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
+
+
+
+
+
+
+### fs/write_text_file
+
+Writes content to a text file in the client's file system.
+
+Only available if the client advertises the `fs.writeTextFile` capability.
+Allows the agent to create or modify files within the client's environment.
+
+See protocol docs: [Client](https://agentclientprotocol.com/protocol/overview#client)
+
+#### WriteTextFileRequest
+
+Request to write content to a text file.
+
+Only available if the client supports the `fs.writeTextFile` capability.
**Type:** Object
@@ -1720,8 +1782,31 @@ these keys.
See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
-ElicitationId} required>
- The ID of the elicitation that completed.
+
+ The text content to write to the file.
+
+
+ Absolute path to the file to write.
+
+SessionId} required>
+ The session ID for this request.
+
+
+#### WriteTextFileResponse
+
+Response to `fs/write_text_file`
+
+**Type:** Object
+
+**Properties:**
+
+
+ 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/extensibility)
+
@@ -3159,71 +3244,6 @@ See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/exte
The file path being modified.
-## ElicitationAcceptAction
-
-**UNSTABLE**
-
-This capability is not part of the spec yet, and may be removed or changed at any point.
-
-The user accepted the elicitation and provided content.
-
-**Type:** Object
-
-**Properties:**
-
-
- The user-provided content, if any, as an object matching the requested schema.
-
-
-## ElicitationAction
-
-**UNSTABLE**
-
-This capability is not part of the spec yet, and may be removed or changed at any point.
-
-The user's action in response to an elicitation.
-
-**Type:** Union
-
-
-The user accepted and provided content.
-
-
-
-
- The discriminator value. Must be `"accept"`.
-
-
- The user-provided content, if any, as an object matching the requested schema.
-
-
-
-
-
-
-The user declined the elicitation.
-
-
-
-
- The discriminator value. Must be `"decline"`.
-
-
-
-
-
-
-The elicitation was cancelled.
-
-
-
-
- The discriminator value. Must be `"cancel"`.
-
-
-
-
-
## ElicitationCapabilities
**UNSTABLE**
@@ -5754,6 +5774,31 @@ A session configuration option selector and its current state.
**Type:** Union
+**Shared properties:**
+
+
+ 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/extensibility)
+
+
+SessionConfigOptionCategory | null>} >
+ Optional semantic category for this option (UX only).
+
+
+ Optional description for the Client to display to the user.
+
+SessionConfigId} required>
+ Unique identifier for the configuration option.
+
+
+ Human-readable label for the option.
+
+
+**Variants:**
+
Single-value selector (dropdown).
diff --git a/docs/rfds/elicitation.mdx b/docs/rfds/elicitation.mdx
index 0987663e..ed6bcd4d 100644
--- a/docs/rfds/elicitation.mdx
+++ b/docs/rfds/elicitation.mdx
@@ -1,5 +1,5 @@
---
-title: "Elicitation: Structured User Input During Sessions"
+title: "Elicitation: Structured User Input"
---
- Author(s): [@yordis](https://github.com/yordis)
@@ -7,7 +7,7 @@ title: "Elicitation: Structured User Input During Sessions"
## Elevator pitch
-Add support for agents to request structured information from users during a session through a standardized elicitation mechanism, aligned with [MCP's elicitation feature](https://modelcontextprotocol.io/specification/draft/client/elicitation). This allows agents to ask follow-up questions, collect authentication credentials, gather preferences, and request required information without side-channel communication or ad-hoc client UI implementations.
+Add support for agents to request structured information from users through a standardized elicitation mechanism, aligned with [MCP's elicitation feature](https://modelcontextprotocol.io/specification/draft/client/elicitation). This allows agents to ask follow-up questions, collect authentication credentials, gather preferences, and request required information without side-channel communication or ad-hoc client UI implementations.
## Status quo
@@ -41,7 +41,7 @@ The mechanism would:
- **Form mode** (in-band): Structured data collection via JSON Schema forms
- **URL mode** (out-of-band): Browser-based flows for sensitive operations like OAuth (addressing PR #330 authentication pain points)
-3. **Request/response pattern**: Agents send elicitation requests via a `session/elicitation` method and receive responses. The agent controls when to send requests and whether to wait for responses before proceeding. Unlike Session Config Options (which are persistent), elicitation requests are transient.
+3. **Request/response pattern**: Agents send elicitation requests via an `elicitation/create` method and receive responses. The agent controls when to send requests and whether to wait for responses before proceeding. Unlike Session Config Options (which are persistent), elicitation requests are transient.
4. **Support client capability negotiation**: Clients declare elicitation support via a structured capability object that distinguishes between `form`-based and `url`-based elicitation (following MCP's capability model). This allows clients to support one or both modalities, enables agents to pass capabilities along to MCP servers, and handles graceful degradation when clients have limited elicitation support.
@@ -70,12 +70,13 @@ Clients can:
### Alignment with MCP
-This proposal follows MCP's draft elicitation specification. See [MCP Elicitation Specification](https://modelcontextprotocol.io/specification/draft/client/elicitation) for detailed guidance. ACP uses the same JSON Schema constraint approach and capability model, adapted for our session/turn-based architecture.
+This proposal follows MCP's draft elicitation specification. See [MCP Elicitation Specification](https://modelcontextprotocol.io/specification/draft/client/elicitation) for detailed guidance. ACP uses the same JSON Schema constraint approach and capability model, adapted for ACP interactions.
Key differences from MCP:
-- MCP elicitation is tool-call-scoped; ACP elicitation is session-scoped
-- ACP uses `session/elicitation` method; MCP uses `elicitation/create`
+- MCP elicitation is tool-call-scoped; ACP elicitation may be tool-call-scoped, session-scoped, or request-scoped
+- ACP uses `elicitation/create` method (same as MCP)
+- ACP includes an optional `toolCallId` field to support tool-call-scoped elicitations (e.g., when an agent receives an MCP elicitation during a tool call and needs to redirect it to the user)
- ACP must integrate with existing Session Config Options (which also use schema constraints)
### Elicitation Request Structure
@@ -346,7 +347,7 @@ Clients use this schema to generate appropriate input forms, validate user input
### Elicitation Request
-The agent sends a `session/elicitation` request when it needs information from the user:
+The agent sends an `elicitation/create` request when it needs information from the user:
**Form mode example:**
@@ -354,9 +355,10 @@ The agent sends a `session/elicitation` request when it needs information from t
{
"jsonrpc": "2.0",
"id": 43,
- "method": "session/elicitation",
+ "method": "elicitation/create",
"params": {
"sessionId": "...",
+ "toolCallId": "tc_123",
"mode": "form",
"message": "How would you like me to approach this refactoring?",
"requestedSchema": {
@@ -385,9 +387,8 @@ The agent sends a `session/elicitation` request when it needs information from t
{
"jsonrpc": "2.0",
"id": 44,
- "method": "session/elicitation",
+ "method": "elicitation/create",
"params": {
- "sessionId": "...",
"mode": "url",
"elicitationId": "github-oauth-001",
"url": "https://agent.example.com/connect?elicitationId=github-oauth-001",
@@ -396,6 +397,40 @@ The agent sends a `session/elicitation` request when it needs information from t
}
```
+`sessionId`, `requestId`, and `toolCallId` are all optional. Elicitation supports three scoping variants:
+
+- **Session elicitation**: `sessionId` is set — tied to a specific session.
+- **Tool call elicitation**: `sessionId` and `toolCallId` are both set — tied to a specific tool call within a session. This is useful when an agent receives an elicitation request from an MCP server during a tool call and needs to redirect it to the user.
+- **Request elicitation**: `requestId` is set — tied to a specific JSON-RPC request outside of a session (e.g., auth/configuration phases before any session is started).
+
+An elicitation may also be sent without any of these fields, in which case it is standalone and not tied to any specific scope.
+
+**Request-scoped example:**
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 45,
+ "method": "elicitation/create",
+ "params": {
+ "requestId": 12,
+ "mode": "form",
+ "message": "Please provide your workspace name to continue setup.",
+ "requestedSchema": {
+ "type": "object",
+ "properties": {
+ "workspaceName": {
+ "type": "string",
+ "title": "Workspace Name",
+ "description": "The name of your workspace"
+ }
+ },
+ "required": ["workspaceName"]
+ }
+ }
+}
+```
+
The client presents the elicitation UI to the user. For form mode, the client generates appropriate input UI based on the JSON Schema. For URL mode, the client opens the URL in a secure browser context.
### User Response
@@ -460,7 +495,7 @@ sequenceDiagram
participant Agent
Note over Agent: Agent initiates elicitation
- Agent->>Client: session/elicitation (mode: form)
+ Agent->>Client: elicitation/create (mode: form)
Note over User,Client: Present elicitation UI
User-->>Client: Provide requested information
@@ -481,7 +516,7 @@ sequenceDiagram
participant Agent
Note over Agent: Agent initiates elicitation
- Agent->>Client: session/elicitation (mode: url)
+ Agent->>Client: elicitation/create (mode: url)
Client->>User: Present consent to open URL
User-->>Client: Provide consent
@@ -491,7 +526,7 @@ sequenceDiagram
Note over User,UserAgent: User interaction
UserAgent-->>Agent: Interaction complete
- Agent-->>Client: notifications/elicitation/complete (optional)
+ Agent-->>Client: elicitation/complete (optional)
Note over Agent: Continue processing with new information
```
@@ -519,19 +554,19 @@ sequenceDiagram
Note over User,UserAgent: User interaction
UserAgent-->>Agent: Interaction complete
- Agent-->>Client: notifications/elicitation/complete (optional)
+ Agent-->>Client: elicitation/complete (optional)
Client->>Agent: Retry original request (optional)
```
### Completion Notifications for URL Mode
-Following MCP, agents MAY send a `notifications/elicitation/complete` notification when an out-of-band interaction started by URL mode elicitation is completed:
+Following MCP, agents MAY send an `elicitation/complete` notification when an out-of-band interaction started by URL mode elicitation is completed:
```json
{
"jsonrpc": "2.0",
- "method": "notifications/elicitation/complete",
+ "method": "elicitation/complete",
"params": {
"elicitationId": "github-oauth-001"
}
@@ -584,7 +619,7 @@ Agents MUST return standard JSON-RPC errors for common failure cases:
Clients MUST return standard JSON-RPC errors for common failure cases:
-- When the agent sends a `session/elicitation` request with a mode not declared in client capabilities: `-32602` (Invalid params)
+- When the agent sends an `elicitation/create` request with a mode not declared in client capabilities: `-32602` (Invalid params)
### Client Capabilities
@@ -725,7 +760,7 @@ From PR #330: URL-mode elicitation allows agents to request authentication witho
7. User authenticates and grants permission
8. OAuth provider redirects back to the agent's redirect_uri
9. Agent exchanges the authorization code for tokens and stores them bound to the user's identity
-10. Agent sends a `notifications/elicitation/complete` notification to inform the client
+10. Agent sends an `elicitation/complete` notification to inform the client
**Key guarantees**:
diff --git a/schema/meta.unstable.json b/schema/meta.unstable.json
index 167bba2f..24b26a94 100644
--- a/schema/meta.unstable.json
+++ b/schema/meta.unstable.json
@@ -26,10 +26,10 @@
"session_set_model": "session/set_model"
},
"clientMethods": {
+ "elicitation_complete": "elicitation/complete",
+ "elicitation_create": "elicitation/create",
"fs_read_text_file": "fs/read_text_file",
"fs_write_text_file": "fs/write_text_file",
- "session_elicitation": "session/elicitation",
- "session_elicitation_complete": "session/elicitation/complete",
"session_request_permission": "session/request_permission",
"session_update": "session/update",
"terminal_create": "terminal/create",
diff --git a/schema/schema.unstable.json b/schema/schema.unstable.json
index 0a6fb847..78c594c5 100644
--- a/schema/schema.unstable.json
+++ b/schema/schema.unstable.json
@@ -149,11 +149,11 @@
{
"allOf": [
{
- "$ref": "#/$defs/ElicitationCompleteNotification"
+ "$ref": "#/$defs/CompleteElicitationNotification"
}
],
"description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nNotification that a URL-based elicitation has completed.",
- "title": "ElicitationCompleteNotification"
+ "title": "CompleteElicitationNotification"
},
{
"allOf": [
@@ -264,11 +264,11 @@
{
"allOf": [
{
- "$ref": "#/$defs/ElicitationRequest"
+ "$ref": "#/$defs/CreateElicitationRequest"
}
],
"description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nRequests structured user input via a form or URL.",
- "title": "ElicitationRequest"
+ "title": "CreateElicitationRequest"
},
{
"allOf": [
@@ -1349,10 +1349,10 @@
{
"allOf": [
{
- "$ref": "#/$defs/ElicitationResponse"
+ "$ref": "#/$defs/CreateElicitationResponse"
}
],
- "title": "ElicitationResponse"
+ "title": "CreateElicitationResponse"
},
{
"allOf": [
@@ -1456,6 +1456,28 @@
"x-method": "session/close",
"x-side": "agent"
},
+ "CompleteElicitationNotification": {
+ "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nNotification sent by the agent when a URL-based elicitation is complete.",
+ "properties": {
+ "_meta": {
+ "additionalProperties": true,
+ "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/extensibility)",
+ "type": ["object", "null"]
+ },
+ "elicitationId": {
+ "allOf": [
+ {
+ "$ref": "#/$defs/ElicitationId"
+ }
+ ],
+ "description": "The ID of the elicitation that completed."
+ }
+ },
+ "required": ["elicitationId"],
+ "type": "object",
+ "x-method": "elicitation/complete",
+ "x-side": "client"
+ },
"ConfigOptionUpdate": {
"description": "Session configuration options have been updated.",
"properties": {
@@ -1623,6 +1645,152 @@
"required": ["amount", "currency"],
"type": "object"
},
+ "CreateElicitationRequest": {
+ "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nRequest from the agent to elicit structured user input.\n\nThe agent sends this to the client to request information from the user,\neither via a form or by directing them to a URL.\nElicitations may be tied to a session, request, or tool call,\nor sent without a specific scope.",
+ "discriminator": {
+ "propertyName": "mode"
+ },
+ "oneOf": [
+ {
+ "allOf": [
+ {
+ "$ref": "#/$defs/ElicitationFormMode"
+ }
+ ],
+ "description": "Form-based elicitation where the client renders a form from the provided schema.",
+ "properties": {
+ "mode": {
+ "const": "form",
+ "type": "string"
+ }
+ },
+ "required": ["mode"],
+ "type": "object"
+ },
+ {
+ "allOf": [
+ {
+ "$ref": "#/$defs/ElicitationUrlMode"
+ }
+ ],
+ "description": "URL-based elicitation where the client directs the user to a URL.",
+ "properties": {
+ "mode": {
+ "const": "url",
+ "type": "string"
+ }
+ },
+ "required": ["mode"],
+ "type": "object"
+ }
+ ],
+ "properties": {
+ "_meta": {
+ "additionalProperties": true,
+ "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/extensibility)",
+ "type": ["object", "null"]
+ },
+ "message": {
+ "description": "A human-readable message describing what input is needed.",
+ "type": "string"
+ },
+ "requestId": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/RequestId"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Optional request ID if this elicitation is tied to a specific request."
+ },
+ "sessionId": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/SessionId"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Optional session ID if this elicitation is tied to a specific session."
+ },
+ "toolCallId": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/ToolCallId"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Optional tool call ID if this elicitation is tied to a specific tool call.\nWhen present, `sessionId` should also be set."
+ }
+ },
+ "required": ["message"],
+ "type": "object",
+ "x-method": "elicitation/create",
+ "x-side": "client"
+ },
+ "CreateElicitationResponse": {
+ "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nResponse from the client to an elicitation request.",
+ "discriminator": {
+ "propertyName": "action"
+ },
+ "oneOf": [
+ {
+ "description": "The user accepted and provided content.",
+ "properties": {
+ "action": {
+ "const": "accept",
+ "type": "string"
+ },
+ "content": {
+ "additionalProperties": {
+ "$ref": "#/$defs/ElicitationContentValue"
+ },
+ "description": "The user-provided content, if any, as an object matching the requested schema.",
+ "type": ["object", "null"]
+ }
+ },
+ "required": ["action"],
+ "type": "object"
+ },
+ {
+ "description": "The user declined the elicitation.",
+ "properties": {
+ "action": {
+ "const": "decline",
+ "type": "string"
+ }
+ },
+ "required": ["action"],
+ "type": "object"
+ },
+ {
+ "description": "The elicitation was cancelled.",
+ "properties": {
+ "action": {
+ "const": "cancel",
+ "type": "string"
+ }
+ },
+ "required": ["action"],
+ "type": "object"
+ }
+ ],
+ "properties": {
+ "_meta": {
+ "additionalProperties": true,
+ "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/extensibility)",
+ "type": ["object", "null"]
+ }
+ },
+ "type": "object",
+ "x-method": "elicitation/create",
+ "x-side": "client"
+ },
"CreateTerminalRequest": {
"description": "Request to create a new terminal and execute a command.",
"properties": {
@@ -1911,65 +2079,6 @@
"required": ["path", "newText"],
"type": "object"
},
- "ElicitationAcceptAction": {
- "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nThe user accepted the elicitation and provided content.",
- "properties": {
- "content": {
- "additionalProperties": {
- "$ref": "#/$defs/ElicitationContentValue"
- },
- "description": "The user-provided content, if any, as an object matching the requested schema.",
- "type": ["object", "null"]
- }
- },
- "type": "object"
- },
- "ElicitationAction": {
- "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nThe user's action in response to an elicitation.",
- "discriminator": {
- "propertyName": "action"
- },
- "oneOf": [
- {
- "allOf": [
- {
- "$ref": "#/$defs/ElicitationAcceptAction"
- }
- ],
- "description": "The user accepted and provided content.",
- "properties": {
- "action": {
- "const": "accept",
- "type": "string"
- }
- },
- "required": ["action"],
- "type": "object"
- },
- {
- "description": "The user declined the elicitation.",
- "properties": {
- "action": {
- "const": "decline",
- "type": "string"
- }
- },
- "required": ["action"],
- "type": "object"
- },
- {
- "description": "The elicitation was cancelled.",
- "properties": {
- "action": {
- "const": "cancel",
- "type": "string"
- }
- },
- "required": ["action"],
- "type": "object"
- }
- ]
- },
"ElicitationCapabilities": {
"description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nElicitation capabilities supported by the client.",
"properties": {
@@ -2003,28 +2112,6 @@
},
"type": "object"
},
- "ElicitationCompleteNotification": {
- "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nNotification sent by the agent when a URL-based elicitation is complete.",
- "properties": {
- "_meta": {
- "additionalProperties": true,
- "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/extensibility)",
- "type": ["object", "null"]
- },
- "elicitationId": {
- "allOf": [
- {
- "$ref": "#/$defs/ElicitationId"
- }
- ],
- "description": "The ID of the elicitation that completed."
- }
- },
- "required": ["elicitationId"],
- "type": "object",
- "x-method": "session/elicitation/complete",
- "x-side": "client"
- },
"ElicitationContentValue": {
"anyOf": [
{
@@ -2172,91 +2259,6 @@
}
]
},
- "ElicitationRequest": {
- "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nRequest from the agent to elicit structured user input.\n\nThe agent sends this to the client to request information from the user,\neither via a form or by directing them to a URL.",
- "discriminator": {
- "propertyName": "mode"
- },
- "oneOf": [
- {
- "allOf": [
- {
- "$ref": "#/$defs/ElicitationFormMode"
- }
- ],
- "description": "Form-based elicitation where the client renders a form from the provided schema.",
- "properties": {
- "mode": {
- "const": "form",
- "type": "string"
- }
- },
- "required": ["mode"],
- "type": "object"
- },
- {
- "allOf": [
- {
- "$ref": "#/$defs/ElicitationUrlMode"
- }
- ],
- "description": "URL-based elicitation where the client directs the user to a URL.",
- "properties": {
- "mode": {
- "const": "url",
- "type": "string"
- }
- },
- "required": ["mode"],
- "type": "object"
- }
- ],
- "properties": {
- "_meta": {
- "additionalProperties": true,
- "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/extensibility)",
- "type": ["object", "null"]
- },
- "message": {
- "description": "A human-readable message describing what input is needed.",
- "type": "string"
- },
- "sessionId": {
- "allOf": [
- {
- "$ref": "#/$defs/SessionId"
- }
- ],
- "description": "The session ID for this request."
- }
- },
- "required": ["sessionId", "message"],
- "type": "object",
- "x-method": "session/elicitation",
- "x-side": "client"
- },
- "ElicitationResponse": {
- "description": "**UNSTABLE**\n\nThis capability is not part of the spec yet, and may be removed or changed at any point.\n\nResponse from the client to an elicitation request.",
- "properties": {
- "_meta": {
- "additionalProperties": true,
- "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/extensibility)",
- "type": ["object", "null"]
- },
- "action": {
- "allOf": [
- {
- "$ref": "#/$defs/ElicitationAction"
- }
- ],
- "description": "The user's action in response to the elicitation."
- }
- },
- "required": ["action"],
- "type": "object",
- "x-method": "session/elicitation",
- "x-side": "client"
- },
"ElicitationSchema": {
"description": "Type-safe elicitation schema for requesting structured user input.\n\nThis represents a JSON Schema object with primitive-typed properties,\nas required by the elicitation specification.",
"properties": {
diff --git a/src/bin/generate.rs b/src/bin/generate.rs
index fd93616b..fdd1e648 100644
--- a/src/bin/generate.rs
+++ b/src/bin/generate.rs
@@ -339,6 +339,17 @@ starting with '$/' it is free to ignore the notification."
writeln!(&mut self.output, "**Type:** Union").unwrap();
writeln!(&mut self.output).unwrap();
+ if let Some(shared_props) = definition.get("properties").and_then(|v| v.as_object())
+ && !shared_props.is_empty()
+ {
+ writeln!(&mut self.output, "**Shared properties:**").unwrap();
+ writeln!(&mut self.output).unwrap();
+ self.document_properties_as_fields(shared_props, definition, 0);
+ writeln!(&mut self.output).unwrap();
+ writeln!(&mut self.output, "**Variants:**").unwrap();
+ writeln!(&mut self.output).unwrap();
+ }
+
let variants = definition
.get("oneOf")
.or_else(|| definition.get("anyOf"))
@@ -1061,10 +1072,10 @@ starting with '$/' it is free to ignore the notification."
"terminal/wait_for_exit" => self.client.get("WaitForTerminalExitRequest").unwrap(),
"terminal/kill" => self.client.get("KillTerminalRequest").unwrap(),
#[cfg(feature = "unstable_elicitation")]
- "session/elicitation" => self.client.get("ElicitationRequest").unwrap(),
+ "elicitation/create" => self.client.get("CreateElicitationRequest").unwrap(),
#[cfg(feature = "unstable_elicitation")]
- "session/elicitation/complete" => {
- self.client.get("ElicitationCompleteNotification").unwrap()
+ "elicitation/complete" => {
+ self.client.get("CompleteElicitationNotification").unwrap()
}
_ => panic!("Introduced a method? Add it here :)"),
}
@@ -1190,4 +1201,70 @@ starting with '$/' it is free to ignore the notification."
side_docs
}
+
+ #[cfg(test)]
+ mod tests {
+ use super::MarkdownGenerator;
+ use serde_json::json;
+
+ #[test]
+ fn document_union_includes_shared_properties() {
+ let mut generator = MarkdownGenerator::new();
+ let definition = json!({
+ "description": "Example union.",
+ "properties": {
+ "message": {
+ "type": "string",
+ "description": "Shared message."
+ }
+ },
+ "required": ["message"],
+ "oneOf": [
+ {
+ "description": "First variant.",
+ "properties": {
+ "mode": {
+ "const": "form",
+ "type": "string"
+ }
+ },
+ "required": ["mode"],
+ "type": "object"
+ },
+ {
+ "description": "Second variant.",
+ "properties": {
+ "mode": {
+ "const": "url",
+ "type": "string"
+ }
+ },
+ "required": ["mode"],
+ "type": "object"
+ }
+ ]
+ });
+
+ generator.document_type(4, "ExampleUnion", &definition);
+
+ assert!(generator.output.contains("**Shared properties:**"));
+ assert!(
+ generator
+ .output
+ .contains("")
+ );
+ assert!(generator.output.contains("Shared message."));
+ assert!(generator.output.contains("**Variants:**"));
+ assert!(
+ generator
+ .output
+ .contains("")
+ );
+ assert!(
+ generator
+ .output
+ .contains("")
+ );
+ }
+ }
}
diff --git a/src/client.rs b/src/client.rs
index 0a34cfe3..652cf4e5 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize};
#[cfg(feature = "unstable_elicitation")]
use crate::elicitation::{
- ElicitationCapabilities, ElicitationCompleteNotification, ElicitationRequest,
- ElicitationResponse,
+ CompleteElicitationNotification, CreateElicitationRequest, CreateElicitationResponse,
+ ElicitationCapabilities,
};
use crate::{
ContentBlock, ExtNotification, ExtRequest, ExtResponse, IntoOption, Meta, Plan,
@@ -1746,12 +1746,12 @@ pub struct ClientMethodNames {
pub terminal_wait_for_exit: &'static str,
/// Method for killing a terminal.
pub terminal_kill: &'static str,
- /// Method for session elicitation.
+ /// Method for elicitation.
#[cfg(feature = "unstable_elicitation")]
- pub session_elicitation: &'static str,
+ pub elicitation_create: &'static str,
/// Notification for elicitation completion.
#[cfg(feature = "unstable_elicitation")]
- pub session_elicitation_complete: &'static str,
+ pub elicitation_complete: &'static str,
}
/// Constant containing all client method names.
@@ -1766,9 +1766,9 @@ pub const CLIENT_METHOD_NAMES: ClientMethodNames = ClientMethodNames {
terminal_wait_for_exit: TERMINAL_WAIT_FOR_EXIT_METHOD_NAME,
terminal_kill: TERMINAL_KILL_METHOD_NAME,
#[cfg(feature = "unstable_elicitation")]
- session_elicitation: SESSION_ELICITATION_METHOD_NAME,
+ elicitation_create: ELICITATION_CREATE_METHOD_NAME,
#[cfg(feature = "unstable_elicitation")]
- session_elicitation_complete: SESSION_ELICITATION_COMPLETE,
+ elicitation_complete: ELICITATION_COMPLETE_NOTIFICATION,
};
/// Notification name for session updates.
@@ -1789,12 +1789,12 @@ pub(crate) const TERMINAL_RELEASE_METHOD_NAME: &str = "terminal/release";
pub(crate) const TERMINAL_WAIT_FOR_EXIT_METHOD_NAME: &str = "terminal/wait_for_exit";
/// Method for killing a terminal.
pub(crate) const TERMINAL_KILL_METHOD_NAME: &str = "terminal/kill";
-/// Method name for session elicitation.
+/// Method name for elicitation.
#[cfg(feature = "unstable_elicitation")]
-pub(crate) const SESSION_ELICITATION_METHOD_NAME: &str = "session/elicitation";
+pub(crate) const ELICITATION_CREATE_METHOD_NAME: &str = "elicitation/create";
/// Notification name for elicitation completion.
#[cfg(feature = "unstable_elicitation")]
-pub(crate) const SESSION_ELICITATION_COMPLETE: &str = "session/elicitation/complete";
+pub(crate) const ELICITATION_COMPLETE_NOTIFICATION: &str = "elicitation/complete";
/// All possible requests that an agent can send to a client.
///
@@ -1890,7 +1890,7 @@ pub enum AgentRequest {
///
/// Requests structured user input via a form or URL.
#[cfg(feature = "unstable_elicitation")]
- ElicitationRequest(ElicitationRequest),
+ CreateElicitationRequest(CreateElicitationRequest),
/// Handles extension method requests from the agent.
///
/// Allows the Agent to send an arbitrary request that is not part of the ACP spec.
@@ -1915,7 +1915,7 @@ impl AgentRequest {
Self::WaitForTerminalExitRequest(_) => CLIENT_METHOD_NAMES.terminal_wait_for_exit,
Self::KillTerminalRequest(_) => CLIENT_METHOD_NAMES.terminal_kill,
#[cfg(feature = "unstable_elicitation")]
- Self::ElicitationRequest(_) => CLIENT_METHOD_NAMES.session_elicitation,
+ Self::CreateElicitationRequest(_) => CLIENT_METHOD_NAMES.elicitation_create,
Self::ExtMethodRequest(ext_request) => &ext_request.method,
}
}
@@ -1941,7 +1941,7 @@ pub enum ClientResponse {
WaitForTerminalExitResponse(WaitForTerminalExitResponse),
KillTerminalResponse(#[serde(default)] KillTerminalResponse),
#[cfg(feature = "unstable_elicitation")]
- ElicitationResponse(ElicitationResponse),
+ CreateElicitationResponse(CreateElicitationResponse),
ExtMethodResponse(ExtResponse),
}
@@ -1975,7 +1975,7 @@ pub enum AgentNotification {
///
/// Notification that a URL-based elicitation has completed.
#[cfg(feature = "unstable_elicitation")]
- ElicitationCompleteNotification(ElicitationCompleteNotification),
+ CompleteElicitationNotification(CompleteElicitationNotification),
/// Handles extension notifications from the agent.
///
/// Allows the Agent to send an arbitrary notification that is not part of the ACP spec.
@@ -1993,9 +1993,7 @@ impl AgentNotification {
match self {
Self::SessionNotification(_) => CLIENT_METHOD_NAMES.session_update,
#[cfg(feature = "unstable_elicitation")]
- Self::ElicitationCompleteNotification(_) => {
- CLIENT_METHOD_NAMES.session_elicitation_complete
- }
+ Self::CompleteElicitationNotification(_) => CLIENT_METHOD_NAMES.elicitation_complete,
Self::ExtNotification(ext_notification) => &ext_notification.method,
}
}
diff --git a/src/elicitation.rs b/src/elicitation.rs
index 55b1d316..030bcbc9 100644
--- a/src/elicitation.rs
+++ b/src/elicitation.rs
@@ -11,8 +11,9 @@ use derive_more::{Display, From};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use crate::client::{SESSION_ELICITATION_COMPLETE, SESSION_ELICITATION_METHOD_NAME};
-use crate::{IntoOption, Meta, SessionId};
+use crate::client::{ELICITATION_COMPLETE_NOTIFICATION, ELICITATION_CREATE_METHOD_NAME};
+use crate::tool_call::ToolCallId;
+use crate::{IntoOption, Meta, RequestId, SessionId};
/// **UNSTABLE**
///
@@ -870,13 +871,23 @@ impl ElicitationUrlCapabilities {
///
/// The agent sends this to the client to request information from the user,
/// either via a form or by directing them to a URL.
+/// Elicitations may be tied to a session, request, or tool call,
+/// or sent without a specific scope.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
-#[schemars(extend("x-side" = "client", "x-method" = SESSION_ELICITATION_METHOD_NAME))]
+#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
-pub struct ElicitationRequest {
- /// The session ID for this request.
- pub session_id: SessionId,
+pub struct CreateElicitationRequest {
+ /// Optional session ID if this elicitation is tied to a specific session.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub session_id: Option,
+ /// Optional request ID if this elicitation is tied to a specific request.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub request_id: Option,
+ /// Optional tool call ID if this elicitation is tied to a specific tool call.
+ /// When present, `sessionId` should also be set.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub tool_call_id: Option,
/// The elicitation mode and its mode-specific fields.
#[serde(flatten)]
pub mode: ElicitationMode,
@@ -891,21 +902,58 @@ pub struct ElicitationRequest {
pub meta: Option,
}
-impl ElicitationRequest {
+impl CreateElicitationRequest {
#[must_use]
- pub fn new(
- session_id: impl Into,
- mode: ElicitationMode,
- message: impl Into,
- ) -> Self {
+ pub fn new(mode: ElicitationMode, message: impl Into) -> Self {
Self {
- session_id: session_id.into(),
+ session_id: None,
+ request_id: None,
+ tool_call_id: None,
mode,
message: message.into(),
meta: None,
}
}
+ /// Sets the session scope for this elicitation.
+ ///
+ /// Clears `request_id` and `tool_call_id` since scopes are mutually exclusive.
+ /// Use [`for_tool_call`](Self::for_tool_call) if you also need a `tool_call_id`.
+ #[must_use]
+ pub fn for_session(mut self, session_id: impl Into) -> Self {
+ self.session_id = Some(session_id.into());
+ self.request_id = None;
+ self.tool_call_id = None;
+ self
+ }
+
+ /// Sets the request scope for this elicitation.
+ ///
+ /// Clears `session_id` and `tool_call_id` since scopes are mutually exclusive.
+ #[must_use]
+ pub fn for_request(mut self, request_id: impl Into) -> Self {
+ self.request_id = Some(request_id.into());
+ self.session_id = None;
+ self.tool_call_id = None;
+ self
+ }
+
+ /// Sets the tool-call scope for this elicitation.
+ ///
+ /// Sets both `session_id` and `tool_call_id`, and clears `request_id`,
+ /// since scopes are mutually exclusive and `tool_call_id` requires a `session_id`.
+ #[must_use]
+ pub fn for_tool_call(
+ mut self,
+ session_id: impl Into,
+ tool_call_id: impl Into,
+ ) -> Self {
+ self.session_id = Some(session_id.into());
+ self.tool_call_id = Some(tool_call_id.into());
+ self.request_id = None;
+ self
+ }
+
/// 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.
@@ -986,11 +1034,12 @@ impl ElicitationUrlMode {
///
/// Response from the client to an elicitation request.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
-#[schemars(extend("x-side" = "client", "x-method" = SESSION_ELICITATION_METHOD_NAME))]
+#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_CREATE_METHOD_NAME))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
-pub struct ElicitationResponse {
+pub struct CreateElicitationResponse {
/// The user's action in response to the elicitation.
+ #[serde(flatten)]
pub action: ElicitationAction,
/// 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
@@ -1001,7 +1050,7 @@ pub struct ElicitationResponse {
pub meta: Option,
}
-impl ElicitationResponse {
+impl CreateElicitationResponse {
#[must_use]
pub fn new(action: ElicitationAction) -> Self {
Self { action, meta: None }
@@ -1026,7 +1075,7 @@ impl ElicitationResponse {
/// The user's action in response to an elicitation.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(tag = "action", rename_all = "snake_case")]
-#[schemars(extend("discriminator" = {"propertyName": "action"}))]
+#[schemars(inline, extend("discriminator" = {"propertyName": "action"}))]
#[non_exhaustive]
pub enum ElicitationAction {
/// The user accepted and provided content.
@@ -1043,6 +1092,7 @@ pub enum ElicitationAction {
///
/// The user accepted the elicitation and provided content.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
+#[schemars(inline)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ElicitationAcceptAction {
@@ -1139,10 +1189,10 @@ impl Default for ElicitationAcceptAction {
///
/// Notification sent by the agent when a URL-based elicitation is complete.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[schemars(extend("x-side" = "client", "x-method" = SESSION_ELICITATION_COMPLETE))]
+#[schemars(extend("x-side" = "client", "x-method" = ELICITATION_COMPLETE_NOTIFICATION))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
-pub struct ElicitationCompleteNotification {
+pub struct CompleteElicitationNotification {
/// The ID of the elicitation that completed.
pub elicitation_id: ElicitationId,
/// The _meta property is reserved by ACP to allow clients and agents to attach additional
@@ -1154,7 +1204,7 @@ pub struct ElicitationCompleteNotification {
pub meta: Option,
}
-impl ElicitationCompleteNotification {
+impl CompleteElicitationNotification {
#[must_use]
pub fn new(elicitation_id: impl Into) -> Self {
Self {
@@ -1250,14 +1300,15 @@ mod tests {
#[test]
fn form_mode_request_serialization() {
let schema = ElicitationSchema::new().string("name", true);
- let req = ElicitationRequest::new(
- "sess_1",
+ let req = CreateElicitationRequest::new(
ElicitationMode::Form(ElicitationFormMode::new(schema)),
"Please enter your name",
);
let json = serde_json::to_value(&req).unwrap();
- assert_eq!(json["sessionId"], "sess_1");
+ assert!(json.get("sessionId").is_none());
+ assert!(json.get("requestId").is_none());
+ assert!(json.get("toolCallId").is_none());
assert_eq!(json["mode"], "form");
assert_eq!(json["message"], "Please enter your name");
assert!(json["requestedSchema"].is_object());
@@ -1267,38 +1318,44 @@ mod tests {
"string"
);
- let roundtripped: ElicitationRequest = serde_json::from_value(json).unwrap();
- assert_eq!(roundtripped.session_id, SessionId::new("sess_1"));
+ let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
+ assert_eq!(roundtripped.session_id, None);
+ assert_eq!(roundtripped.request_id, None);
+ assert_eq!(roundtripped.tool_call_id, None);
assert_eq!(roundtripped.message, "Please enter your name");
assert!(matches!(roundtripped.mode, ElicitationMode::Form(_)));
}
#[test]
fn url_mode_request_serialization() {
- let req = ElicitationRequest::new(
- "sess_2",
+ let req = CreateElicitationRequest::new(
ElicitationMode::Url(ElicitationUrlMode::new(
"elic_1",
"https://example.com/auth",
)),
"Please authenticate",
- );
+ )
+ .for_tool_call("sess_2", "tc_1");
let json = serde_json::to_value(&req).unwrap();
assert_eq!(json["sessionId"], "sess_2");
+ assert!(json.get("requestId").is_none());
+ assert_eq!(json["toolCallId"], "tc_1");
assert_eq!(json["mode"], "url");
assert_eq!(json["elicitationId"], "elic_1");
assert_eq!(json["url"], "https://example.com/auth");
assert_eq!(json["message"], "Please authenticate");
- let roundtripped: ElicitationRequest = serde_json::from_value(json).unwrap();
- assert_eq!(roundtripped.session_id, SessionId::new("sess_2"));
+ let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
+ assert_eq!(roundtripped.session_id, Some(SessionId::new("sess_2")));
+ assert_eq!(roundtripped.request_id, None);
+ assert_eq!(roundtripped.tool_call_id, Some(ToolCallId::new("tc_1")));
assert!(matches!(roundtripped.mode, ElicitationMode::Url(_)));
}
#[test]
fn response_accept_serialization() {
- let resp = ElicitationResponse::new(ElicitationAction::Accept(
+ let resp = CreateElicitationResponse::new(ElicitationAction::Accept(
ElicitationAcceptAction::new().content(BTreeMap::from([(
"name".to_string(),
ElicitationContentValue::from("Alice"),
@@ -1306,10 +1363,10 @@ mod tests {
));
let json = serde_json::to_value(&resp).unwrap();
- assert_eq!(json["action"]["action"], "accept");
- assert_eq!(json["action"]["content"]["name"], "Alice");
+ assert_eq!(json["action"], "accept");
+ assert_eq!(json["content"]["name"], "Alice");
- let roundtripped: ElicitationResponse = serde_json::from_value(json).unwrap();
+ let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
assert!(matches!(
roundtripped.action,
ElicitationAction::Accept(ElicitationAcceptAction {
@@ -1321,34 +1378,126 @@ mod tests {
#[test]
fn response_decline_serialization() {
- let resp = ElicitationResponse::new(ElicitationAction::Decline);
+ let resp = CreateElicitationResponse::new(ElicitationAction::Decline);
let json = serde_json::to_value(&resp).unwrap();
- assert_eq!(json["action"]["action"], "decline");
+ assert_eq!(json["action"], "decline");
- let roundtripped: ElicitationResponse = serde_json::from_value(json).unwrap();
+ let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
assert!(matches!(roundtripped.action, ElicitationAction::Decline));
}
#[test]
fn response_cancel_serialization() {
- let resp = ElicitationResponse::new(ElicitationAction::Cancel);
+ let resp = CreateElicitationResponse::new(ElicitationAction::Cancel);
let json = serde_json::to_value(&resp).unwrap();
- assert_eq!(json["action"]["action"], "cancel");
+ assert_eq!(json["action"], "cancel");
- let roundtripped: ElicitationResponse = serde_json::from_value(json).unwrap();
+ let roundtripped: CreateElicitationResponse = serde_json::from_value(json).unwrap();
assert!(matches!(roundtripped.action, ElicitationAction::Cancel));
}
+ #[test]
+ fn session_only_request_serialization() {
+ let req = CreateElicitationRequest::new(
+ ElicitationMode::Form(ElicitationFormMode::new(
+ ElicitationSchema::new().string("name", true),
+ )),
+ "Enter your name",
+ )
+ .for_session("sess_1");
+
+ let json = serde_json::to_value(&req).unwrap();
+ assert_eq!(json["sessionId"], "sess_1");
+ assert!(json.get("requestId").is_none());
+ assert!(json.get("toolCallId").is_none());
+
+ let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
+ assert_eq!(roundtripped.session_id, Some(SessionId::new("sess_1")));
+ assert_eq!(roundtripped.request_id, None);
+ assert_eq!(roundtripped.tool_call_id, None);
+ }
+
+ #[test]
+ fn request_only_request_serialization() {
+ let req = CreateElicitationRequest::new(
+ ElicitationMode::Form(ElicitationFormMode::new(
+ ElicitationSchema::new().string("workspace", true),
+ )),
+ "Enter workspace name",
+ )
+ .for_request(99i64);
+
+ let json = serde_json::to_value(&req).unwrap();
+ assert!(json.get("sessionId").is_none());
+ assert_eq!(json["requestId"], 99);
+ assert!(json.get("toolCallId").is_none());
+
+ let roundtripped: CreateElicitationRequest = serde_json::from_value(json).unwrap();
+ assert_eq!(roundtripped.session_id, None);
+ assert_eq!(roundtripped.request_id, Some(RequestId::Number(99)));
+ assert_eq!(roundtripped.tool_call_id, None);
+ }
+
+ #[test]
+ fn for_tool_call_sets_both_ids() {
+ let req = CreateElicitationRequest::new(
+ ElicitationMode::Form(ElicitationFormMode::new(
+ ElicitationSchema::new().string("name", true),
+ )),
+ "Enter name",
+ )
+ .for_tool_call("sess_1", "tc_42");
+
+ assert_eq!(req.session_id, Some(SessionId::new("sess_1")));
+ assert_eq!(req.tool_call_id, Some(ToolCallId::new("tc_42")));
+ assert_eq!(req.request_id, None);
+
+ let json = serde_json::to_value(&req).unwrap();
+ assert_eq!(json["sessionId"], "sess_1");
+ assert_eq!(json["toolCallId"], "tc_42");
+ assert!(json.get("requestId").is_none());
+ }
+
+ #[test]
+ fn scope_setters_are_mutually_exclusive() {
+ let base = || {
+ CreateElicitationRequest::new(
+ ElicitationMode::Form(ElicitationFormMode::new(
+ ElicitationSchema::new().string("name", true),
+ )),
+ "msg",
+ )
+ };
+
+ // session_id clears request_id and tool_call_id
+ let req = base().for_request(1i64).for_session("s1");
+ assert_eq!(req.session_id, Some(SessionId::new("s1")));
+ assert_eq!(req.request_id, None);
+ assert_eq!(req.tool_call_id, None);
+
+ // request_id clears session_id and tool_call_id
+ let req = base().for_tool_call("s1", "tc1").for_request(2i64);
+ assert_eq!(req.request_id, Some(RequestId::Number(2)));
+ assert_eq!(req.session_id, None);
+ assert_eq!(req.tool_call_id, None);
+
+ // for_tool_call clears request_id
+ let req = base().for_request(3i64).for_tool_call("s2", "tc2");
+ assert_eq!(req.session_id, Some(SessionId::new("s2")));
+ assert_eq!(req.tool_call_id, Some(ToolCallId::new("tc2")));
+ assert_eq!(req.request_id, None);
+ }
+
#[test]
fn completion_notification_serialization() {
- let notif = ElicitationCompleteNotification::new("elic_1");
+ let notif = CompleteElicitationNotification::new("elic_1");
let json = serde_json::to_value(¬if).unwrap();
assert_eq!(json["elicitationId"], "elic_1");
- let roundtripped: ElicitationCompleteNotification = serde_json::from_value(json).unwrap();
+ let roundtripped: CompleteElicitationNotification = serde_json::from_value(json).unwrap();
assert_eq!(roundtripped.elicitation_id, ElicitationId::new("elic_1"));
}
@@ -1642,11 +1791,9 @@ mod tests {
#[test]
fn response_accept_rejects_non_object_content() {
- let err = serde_json::from_value::(json!({
- "action": {
- "action": "accept",
- "content": "Alice"
- }
+ let err = serde_json::from_value::(json!({
+ "action": "accept",
+ "content": "Alice"
}))
.unwrap_err();
@@ -1655,13 +1802,11 @@ mod tests {
#[test]
fn response_accept_rejects_nested_object_content() {
- let err = serde_json::from_value::(json!({
- "action": {
- "action": "accept",
- "content": {
- "profile": {
- "name": "Alice"
- }
+ let err = serde_json::from_value::(json!({
+ "action": "accept",
+ "content": {
+ "profile": {
+ "name": "Alice"
}
}
}))
@@ -1672,7 +1817,7 @@ mod tests {
#[test]
fn response_accept_allows_primitive_and_string_array_content() {
- let response = ElicitationResponse::new(ElicitationAction::Accept(
+ let response = CreateElicitationResponse::new(ElicitationAction::Accept(
ElicitationAcceptAction::new().content(BTreeMap::from([
("name".to_string(), ElicitationContentValue::from("Alice")),
("age".to_string(), ElicitationContentValue::from(30_i32)),
@@ -1689,11 +1834,12 @@ mod tests {
));
let json = serde_json::to_value(&response).unwrap();
- assert_eq!(json["action"]["content"]["name"], "Alice");
- assert_eq!(json["action"]["content"]["age"], 30);
- assert_eq!(json["action"]["content"]["score"], 9.5);
- assert_eq!(json["action"]["content"]["subscribed"], true);
- assert_eq!(json["action"]["content"]["tags"][0], "rust");
- assert_eq!(json["action"]["content"]["tags"][1], "acp");
+ assert_eq!(json["action"], "accept");
+ assert_eq!(json["content"]["name"], "Alice");
+ assert_eq!(json["content"]["age"], 30);
+ assert_eq!(json["content"]["score"], 9.5);
+ assert_eq!(json["content"]["subscribed"], true);
+ assert_eq!(json["content"]["tags"][0], "rust");
+ assert_eq!(json["content"]["tags"][1], "acp");
}
}
diff --git a/src/rpc.rs b/src/rpc.rs
index 4df1d484..ae52d616 100644
--- a/src/rpc.rs
+++ b/src/rpc.rs
@@ -216,8 +216,8 @@ impl Side for ClientSide {
.map_err(Into::into)
}
#[cfg(feature = "unstable_elicitation")]
- m if m == CLIENT_METHOD_NAMES.session_elicitation => serde_json::from_str(params.get())
- .map(AgentRequest::ElicitationRequest)
+ m if m == CLIENT_METHOD_NAMES.elicitation_create => serde_json::from_str(params.get())
+ .map(AgentRequest::CreateElicitationRequest)
.map_err(Into::into),
_ => {
if let Some(custom_method) = method.strip_prefix('_') {
@@ -240,9 +240,9 @@ impl Side for ClientSide {
.map(AgentNotification::SessionNotification)
.map_err(Into::into),
#[cfg(feature = "unstable_elicitation")]
- m if m == CLIENT_METHOD_NAMES.session_elicitation_complete => {
+ m if m == CLIENT_METHOD_NAMES.elicitation_complete => {
serde_json::from_str(params.get())
- .map(AgentNotification::ElicitationCompleteNotification)
+ .map(AgentNotification::CompleteElicitationNotification)
.map_err(Into::into)
}
_ => {