diff --git a/docs/protocol/schema.mdx b/docs/protocol/schema.mdx index b24163ea..4793c6ba 100644 --- a/docs/protocol/schema.mdx +++ b/docs/protocol/schema.mdx @@ -44,6 +44,18 @@ Specifies which authentication method to use. advertised in the initialize response. +#### AuthenticateResponse + +Response to authenticate method + +**Type:** Object + +**Properties:** + + + Extension point for implementations + + ### initialize Establishes the connection with a client and negotiates protocol capabilities. @@ -210,6 +222,24 @@ See protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/s The ID of the session to load. +#### LoadSessionResponse + +Response from loading an existing session. + +**Type:** Object + +**Properties:** + + + Extension point for implementations + +SessionModeState | null} > + **UNSTABLE** + +This field is not part of the spec, and may be removed or changed at any point. + + + ### session/new @@ -406,6 +436,19 @@ Only available if the client supports the `fs.readTextFile` capability. The session ID for this request. +#### ReadTextFileResponse + +Response containing the contents of a text file. + +**Type:** Object + +**Properties:** + + + Extension point for implementations + + + ### fs/write_text_file @@ -443,6 +486,18 @@ Only available if the client supports the `fs.writeTextFile` capability. The session ID for this request. +#### WriteTextFileResponse + +Response to fs/write_text_file + +**Type:** Object + +**Properties:** + + + Extension point for implementations + + ### session/request_permission @@ -676,6 +731,18 @@ Request to kill a terminal command without releasing the terminal. The ID of the terminal to kill. +#### KillTerminalCommandResponse + +Response to terminal/kill command method + +**Type:** Object + +**Properties:** + + + Extension point for implementations + + ### terminal/output @@ -777,6 +844,18 @@ Request to release a terminal and free its resources. The ID of the terminal to release. +#### ReleaseTerminalResponse + +Response to terminal/release method + +**Type:** Object + +**Properties:** + + + Extension point for implementations + + ### terminal/wait_for_exit @@ -1346,24 +1425,6 @@ An image provided to or from an LLM. -## LoadSessionResponse - -Response from loading an existing session. - -**Type:** Object - -**Properties:** - - - Extension point for implementations - -SessionModeState | null} > - **UNSTABLE** - -This field is not part of the spec, and may be removed or changed at any point. - - - ## McpCapabilities MCP capabilities supported by the agent @@ -1723,19 +1784,6 @@ Non-breaking changes should be introduced via capabilities. | Minimum | `0` | | Maximum | `65535` | -## ReadTextFileResponse - -Response containing the contents of a text file. - -**Type:** Object - -**Properties:** - - - Extension point for implementations - - - ## RequestPermissionOutcome The outcome of a permission request. @@ -2142,7 +2190,11 @@ The current mode of the session has changed This type is not part of the spec, and may be removed or changed at any point. -**Type:** `object` +**Type:** Object + +**Properties:** + + ## StopReason diff --git a/rust/acp.rs b/rust/acp.rs index fb66ed2e..84e5ce95 100644 --- a/rust/acp.rs +++ b/rust/acp.rs @@ -176,13 +176,17 @@ impl Agent for ClientSideConnection { .await } - async fn authenticate(&self, arguments: AuthenticateRequest) -> Result<(), Error> { + async fn authenticate( + &self, + arguments: AuthenticateRequest, + ) -> Result { self.conn - .request( + .request::>( AUTHENTICATE_METHOD_NAME, Some(ClientRequest::AuthenticateRequest(arguments)), ) .await + .map(|value| value.unwrap_or_default()) } async fn new_session(&self, arguments: NewSessionRequest) -> Result { @@ -199,11 +203,12 @@ impl Agent for ClientSideConnection { arguments: LoadSessionRequest, ) -> Result { self.conn - .request( + .request::>( SESSION_LOAD_METHOD_NAME, Some(ClientRequest::LoadSessionRequest(arguments)), ) .await + .map(|value| value.unwrap_or_default()) } #[cfg(feature = "unstable")] @@ -349,8 +354,8 @@ impl MessageHandler for T { Ok(ClientResponse::RequestPermissionResponse(response)) } AgentRequest::WriteTextFileRequest(args) => { - self.write_text_file(args).await?; - Ok(ClientResponse::WriteTextFileResponse) + let response = self.write_text_file(args).await?; + Ok(ClientResponse::WriteTextFileResponse(response)) } AgentRequest::ReadTextFileRequest(args) => { let response = self.read_text_file(args).await?; @@ -365,16 +370,16 @@ impl MessageHandler for T { Ok(ClientResponse::TerminalOutputResponse(response)) } AgentRequest::ReleaseTerminalRequest(args) => { - self.release_terminal(args).await?; - Ok(ClientResponse::ReleaseTerminalResponse) + let response = self.release_terminal(args).await?; + Ok(ClientResponse::ReleaseTerminalResponse(response)) } AgentRequest::WaitForTerminalExitRequest(args) => { let response = self.wait_for_terminal_exit(args).await?; Ok(ClientResponse::WaitForTerminalExitResponse(response)) } AgentRequest::KillTerminalCommandRequest(args) => { - self.kill_terminal_command(args).await?; - Ok(ClientResponse::KillTerminalResponse) + let response = self.kill_terminal_command(args).await?; + Ok(ClientResponse::KillTerminalResponse(response)) } AgentRequest::ExtMethodRequest(args) => { let response = self.ext_method(args.method, args.params).await?; @@ -466,13 +471,17 @@ impl Client for AgentSideConnection { .await } - async fn write_text_file(&self, arguments: WriteTextFileRequest) -> Result<(), Error> { + async fn write_text_file( + &self, + arguments: WriteTextFileRequest, + ) -> Result { self.conn - .request( + .request::>( FS_WRITE_TEXT_FILE_METHOD_NAME, Some(AgentRequest::WriteTextFileRequest(arguments)), ) .await + .map(|value| value.unwrap_or_default()) } async fn read_text_file( @@ -511,13 +520,17 @@ impl Client for AgentSideConnection { .await } - async fn release_terminal(&self, arguments: ReleaseTerminalRequest) -> Result<(), Error> { + async fn release_terminal( + &self, + arguments: ReleaseTerminalRequest, + ) -> Result { self.conn - .request( + .request::>( TERMINAL_RELEASE_METHOD_NAME, Some(AgentRequest::ReleaseTerminalRequest(arguments)), ) .await + .map(|value| value.unwrap_or_default()) } async fn wait_for_terminal_exit( @@ -535,13 +548,14 @@ impl Client for AgentSideConnection { async fn kill_terminal_command( &self, arguments: KillTerminalCommandRequest, - ) -> Result<(), Error> { + ) -> Result { self.conn - .request( + .request::>( TERMINAL_KILL_METHOD_NAME, Some(AgentRequest::KillTerminalCommandRequest(arguments)), ) .await + .map(|value| value.unwrap_or_default()) } async fn session_notification(&self, notification: SessionNotification) -> Result<(), Error> { @@ -657,8 +671,8 @@ impl MessageHandler for T { Ok(AgentResponse::InitializeResponse(response)) } ClientRequest::AuthenticateRequest(args) => { - self.authenticate(args).await?; - Ok(AgentResponse::AuthenticateResponse) + let response = self.authenticate(args).await?; + Ok(AgentResponse::AuthenticateResponse(response)) } ClientRequest::NewSessionRequest(args) => { let response = self.new_session(args).await?; diff --git a/rust/agent.rs b/rust/agent.rs index 0b9da83e..3d1a3019 100644 --- a/rust/agent.rs +++ b/rust/agent.rs @@ -45,7 +45,7 @@ pub trait Agent { fn authenticate( &self, arguments: AuthenticateRequest, - ) -> impl Future>; + ) -> impl Future>; /// Creates a new conversation session with the agent. /// @@ -150,7 +150,7 @@ pub trait Agent { /// /// See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "initialize"))] +#[schemars(extend("x-side" = "agent", "x-method" = INITIALIZE_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct InitializeRequest { /// The latest protocol version supported by the client. @@ -169,7 +169,7 @@ pub struct InitializeRequest { /// /// See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "initialize"))] +#[schemars(extend("x-side" = "agent", "x-method" = INITIALIZE_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct InitializeResponse { /// The protocol version the client specified if supported by the agent, @@ -194,7 +194,7 @@ pub struct InitializeResponse { /// /// Specifies which authentication method to use. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "authenticate"))] +#[schemars(extend("x-side" = "agent", "x-method" = AUTHENTICATE_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct AuthenticateRequest { /// The ID of the authentication method to use. @@ -205,6 +205,16 @@ pub struct AuthenticateRequest { pub meta: Option, } +/// Response to authenticate method +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +#[schemars(extend("x-side" = "agent", "x-method" = AUTHENTICATE_METHOD_NAME))] +pub struct AuthenticateResponse { + /// Extension point for implementations + #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + pub meta: Option, +} + /// Unique identifier for an authentication method. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)] #[serde(transparent)] @@ -231,7 +241,7 @@ pub struct AuthMethod { /// /// See protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol/session-setup#creating-a-session) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "session/new"))] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct NewSessionRequest { /// The working directory for this session. Must be an absolute path. @@ -247,7 +257,7 @@ pub struct NewSessionRequest { /// /// See protocol docs: [Creating a Session](https://agentclientprotocol.com/protocol/session-setup#creating-a-session) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "session/new"))] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_NEW_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct NewSessionResponse { /// Unique identifier for the created session. @@ -272,7 +282,7 @@ pub struct NewSessionResponse { /// /// See protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/session-setup#loading-sessions) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "session/load"))] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_LOAD_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct LoadSessionRequest { /// List of MCP servers to connect to for this session. @@ -287,7 +297,8 @@ pub struct LoadSessionRequest { } /// Response from loading an existing session. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_LOAD_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct LoadSessionResponse { /// **UNSTABLE** @@ -360,9 +371,11 @@ pub struct SetSessionModeRequest { /// **UNSTABLE** /// /// This type is not part of the spec, and may be removed or changed at any point. -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct SetSessionModeResponse {} +pub struct SetSessionModeResponse { + pub meta: Option, +} // MCP @@ -449,7 +462,7 @@ pub struct HttpHeader { /// /// See protocol docs: [User Message](https://agentclientprotocol.com/protocol/prompt-turn#1-user-message) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "session/prompt"))] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct PromptRequest { /// The ID of the session to send this user message to @@ -477,7 +490,7 @@ pub struct PromptRequest { /// /// See protocol docs: [Check for Completion](https://agentclientprotocol.com/protocol/prompt-turn#4-check-for-completion) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "session/prompt"))] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_PROMPT_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct PromptResponse { /// Indicates why the agent stopped processing the turn. @@ -668,11 +681,11 @@ pub enum ClientRequest { #[schemars(extend("x-docs-ignore" = true))] pub enum AgentResponse { InitializeResponse(InitializeResponse), - AuthenticateResponse, + AuthenticateResponse(#[serde(default)] AuthenticateResponse), NewSessionResponse(NewSessionResponse), - LoadSessionResponse(LoadSessionResponse), + LoadSessionResponse(#[serde(default)] LoadSessionResponse), #[cfg(feature = "unstable")] - SetSessionModeResponse(SetSessionModeResponse), + SetSessionModeResponse(#[serde(default)] SetSessionModeResponse), PromptResponse(PromptResponse), ExtMethodResponse(#[schemars(with = "serde_json::Value")] Arc), } @@ -695,7 +708,7 @@ pub enum ClientNotification { /// /// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "agent", "x-method" = "session/cancel"))] +#[schemars(extend("x-side" = "agent", "x-method" = SESSION_CANCEL_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct CancelNotification { /// The ID of the session to cancel operations for. diff --git a/rust/client.rs b/rust/client.rs index 6908f578..cc9eb976 100644 --- a/rust/client.rs +++ b/rust/client.rs @@ -45,7 +45,7 @@ pub trait Client { fn write_text_file( &self, args: WriteTextFileRequest, - ) -> impl Future>; + ) -> impl Future>; /// Reads content from a text file in the client's file system. /// @@ -119,7 +119,7 @@ pub trait Client { fn release_terminal( &self, args: ReleaseTerminalRequest, - ) -> impl Future>; + ) -> impl Future>; /// Waits for the terminal command to exit and return its exit status /// @@ -144,7 +144,7 @@ pub trait Client { fn kill_terminal_command( &self, args: KillTerminalCommandRequest, - ) -> impl Future>; + ) -> impl Future>; /// Handles extension method requests from the agent. /// @@ -181,7 +181,7 @@ pub trait Client { /// /// See protocol docs: [Agent Reports Output](https://agentclientprotocol.com/protocol/prompt-turn#3-agent-reports-output) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "client", "x-method" = "session/update"))] +#[schemars(extend("x-side" = "client", "x-method" = SESSION_UPDATE_NOTIFICATION))] #[serde(rename_all = "camelCase")] pub struct SessionNotification { /// The ID of the session this update pertains to. @@ -264,7 +264,7 @@ pub enum AvailableCommandInput { /// /// See protocol docs: [Requesting Permission](https://agentclientprotocol.com/protocol/tool-calls#requesting-permission) #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "client", "x-method" = "session/request_permission"))] +#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct RequestPermissionRequest { /// The session ID for this request. @@ -322,7 +322,7 @@ pub enum PermissionOptionKind { /// Response to a permission request. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "client", "x-method" = "session/request_permission"))] +#[schemars(extend("x-side" = "client", "x-method" = SESSION_REQUEST_PERMISSION_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct RequestPermissionResponse { /// The user's decision on the permission request. @@ -359,7 +359,7 @@ pub enum RequestPermissionOutcome { /// /// Only available if the client supports the `fs.writeTextFile` capability. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "client", "x-method" = "fs/write_text_file"))] +#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct WriteTextFileRequest { /// The session ID for this request. @@ -373,13 +373,24 @@ pub struct WriteTextFileRequest { pub meta: Option, } +/// Response to fs/write_text_file +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +#[schemars(extend("x-side" = "client", "x-method" = FS_WRITE_TEXT_FILE_METHOD_NAME))] +#[serde(default)] +pub struct WriteTextFileResponse { + /// Extension point for implementations + #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + pub meta: Option, +} + // Read text file /// Request to read content from a text file. /// /// Only available if the client supports the `fs.readTextFile` capability. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -#[schemars(extend("x-side" = "client", "x-method" = "fs/read_text_file"))] +#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct ReadTextFileRequest { /// The session ID for this request. @@ -399,6 +410,7 @@ pub struct ReadTextFileRequest { /// Response containing the contents of a text file. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[schemars(extend("x-side" = "client", "x-method" = FS_READ_TEXT_FILE_METHOD_NAME))] #[serde(rename_all = "camelCase")] pub struct ReadTextFileResponse { pub content: String, @@ -509,6 +521,16 @@ pub struct ReleaseTerminalRequest { pub meta: Option, } +/// Response to terminal/release method +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_RELEASE_METHOD_NAME))] +pub struct ReleaseTerminalResponse { + /// Extension point for implementations + #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + pub meta: Option, +} + /// Request to kill a terminal command without releasing the terminal. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -523,6 +545,16 @@ pub struct KillTerminalCommandRequest { pub meta: Option, } +/// Response to terminal/kill command method +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +#[schemars(extend("x-side" = "client", "x-method" = TERMINAL_KILL_METHOD_NAME))] +pub struct KillTerminalCommandResponse { + /// Extension point for implementations + #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] + pub meta: Option, +} + /// Request to wait for a terminal command to exit. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -694,14 +726,14 @@ pub enum AgentRequest { #[serde(untagged)] #[schemars(extend("x-docs-ignore" = true))] pub enum ClientResponse { - WriteTextFileResponse, + WriteTextFileResponse(#[serde(default)] WriteTextFileResponse), ReadTextFileResponse(ReadTextFileResponse), RequestPermissionResponse(RequestPermissionResponse), CreateTerminalResponse(CreateTerminalResponse), TerminalOutputResponse(TerminalOutputResponse), - ReleaseTerminalResponse, + ReleaseTerminalResponse(#[serde(default)] ReleaseTerminalResponse), WaitForTerminalExitResponse(WaitForTerminalExitResponse), - KillTerminalResponse, + KillTerminalResponse(#[serde(default)] KillTerminalCommandResponse), ExtMethodResponse(#[schemars(with = "serde_json::Value")] Arc), } @@ -713,8 +745,8 @@ pub enum ClientResponse { /// Notifications do not expect a response. #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[serde(untagged)] -#[schemars(extend("x-docs-ignore" = true))] #[allow(clippy::large_enum_variant)] +#[schemars(extend("x-docs-ignore" = true))] pub enum AgentNotification { SessionNotification(SessionNotification), ExtNotification(ExtMethod), diff --git a/rust/error.rs b/rust/error.rs index 6a772492..4469d8cd 100644 --- a/rust/error.rs +++ b/rust/error.rs @@ -33,9 +33,6 @@ pub struct Error { /// This may include debugging information or context-specific details. #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, - /// Extension point for implementations - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] - pub meta: Option, } impl Error { @@ -48,7 +45,6 @@ impl Error { code, message, data: None, - meta: None, } } @@ -109,9 +105,6 @@ pub struct ErrorCode { pub code: i32, /// The standard error message for this code. pub message: &'static str, - /// Extension point for implementations - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] - pub meta: Option, } impl ErrorCode { @@ -120,28 +113,24 @@ impl ErrorCode { pub const PARSE_ERROR: ErrorCode = ErrorCode { code: -32700, message: "Parse error", - meta: None, }; /// The JSON sent is not a valid Request object. pub const INVALID_REQUEST: ErrorCode = ErrorCode { code: -32600, message: "Invalid Request", - meta: None, }; /// The method does not exist or is not available. pub const METHOD_NOT_FOUND: ErrorCode = ErrorCode { code: -32601, message: "Method not found", - meta: None, }; /// Invalid method parameter(s). pub const INVALID_PARAMS: ErrorCode = ErrorCode { code: -32602, message: "Invalid params", - meta: None, }; /// Internal JSON-RPC error. @@ -149,7 +138,6 @@ impl ErrorCode { pub const INTERNAL_ERROR: ErrorCode = ErrorCode { code: -32603, message: "Internal error", - meta: None, }; /// Authentication is required before this operation can be performed. @@ -157,7 +145,6 @@ impl ErrorCode { pub const AUTH_REQUIRED: ErrorCode = ErrorCode { code: -32000, message: "Authentication required", - meta: None, }; } diff --git a/rust/example_agent.rs b/rust/example_agent.rs index ff439f8d..1614e3a0 100644 --- a/rust/example_agent.rs +++ b/rust/example_agent.rs @@ -14,7 +14,7 @@ use std::{cell::Cell, sync::Arc}; -use agent_client_protocol::{self as acp, Client, SessionNotification}; +use agent_client_protocol::{self as acp, AuthenticateResponse, Client, SessionNotification}; use serde_json::{json, value::RawValue}; use tokio::sync::{mpsc, oneshot}; use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _}; @@ -49,9 +49,12 @@ impl acp::Agent for ExampleAgent { }) } - async fn authenticate(&self, arguments: acp::AuthenticateRequest) -> Result<(), acp::Error> { + async fn authenticate( + &self, + arguments: acp::AuthenticateRequest, + ) -> Result { log::info!("Received authenticate request {arguments:?}"); - Ok(()) + Ok(Default::default()) } async fn new_session( diff --git a/rust/example_client.rs b/rust/example_client.rs index c6432ae7..71e39bde 100644 --- a/rust/example_client.rs +++ b/rust/example_client.rs @@ -14,7 +14,7 @@ use std::sync::Arc; -use agent_client_protocol::{self as acp, Agent}; +use agent_client_protocol::{self as acp, Agent, KillTerminalCommandResponse}; use anyhow::bail; use serde_json::value::RawValue; use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt}; @@ -32,7 +32,7 @@ impl acp::Client for ExampleClient { async fn write_text_file( &self, _args: acp::WriteTextFileRequest, - ) -> anyhow::Result<(), acp::Error> { + ) -> anyhow::Result { Err(acp::Error::method_not_found()) } @@ -60,7 +60,7 @@ impl acp::Client for ExampleClient { async fn release_terminal( &self, _args: acp::ReleaseTerminalRequest, - ) -> anyhow::Result<(), acp::Error> { + ) -> anyhow::Result { Err(acp::Error::method_not_found()) } @@ -74,7 +74,7 @@ impl acp::Client for ExampleClient { async fn kill_terminal_command( &self, _args: acp::KillTerminalCommandRequest, - ) -> anyhow::Result<(), acp::Error> { + ) -> anyhow::Result { Err(acp::Error::method_not_found()) } diff --git a/rust/rpc_tests.rs b/rust/rpc_tests.rs index 5e905cd9..4c400e5e 100644 --- a/rust/rpc_tests.rs +++ b/rust/rpc_tests.rs @@ -56,12 +56,15 @@ impl Client for TestClient { }) } - async fn write_text_file(&self, arguments: WriteTextFileRequest) -> Result<(), Error> { + async fn write_text_file( + &self, + arguments: WriteTextFileRequest, + ) -> Result { self.written_files .lock() .unwrap() .push((arguments.path, arguments.content)); - Ok(()) + Ok(Default::default()) } async fn read_text_file( @@ -98,11 +101,17 @@ impl Client for TestClient { unimplemented!() } - async fn kill_terminal_command(&self, _args: KillTerminalCommandRequest) -> Result<(), Error> { + async fn kill_terminal_command( + &self, + _args: KillTerminalCommandRequest, + ) -> Result { unimplemented!() } - async fn release_terminal(&self, _args: ReleaseTerminalRequest) -> Result<(), Error> { + async fn release_terminal( + &self, + _args: ReleaseTerminalRequest, + ) -> Result { unimplemented!() } @@ -171,8 +180,11 @@ impl Agent for TestAgent { }) } - async fn authenticate(&self, _arguments: AuthenticateRequest) -> Result<(), Error> { - Ok(()) + async fn authenticate( + &self, + _arguments: AuthenticateRequest, + ) -> Result { + Ok(Default::default()) } async fn new_session( diff --git a/rust/tool_call.rs b/rust/tool_call.rs index fb5e2240..95f46a8a 100644 --- a/rust/tool_call.rs +++ b/rust/tool_call.rs @@ -128,9 +128,6 @@ pub struct ToolCallUpdateFields { /// Update the raw output. #[serde(default, skip_serializing_if = "Option::is_none")] pub raw_output: Option, - /// Extension point for implementations - #[serde(skip_serializing_if = "Option::is_none", rename = "_meta")] - pub meta: Option, } /// If a given tool call doesn't exist yet, allows for attempting to construct @@ -150,7 +147,6 @@ impl TryFrom for ToolCall { locations, raw_input, raw_output, - meta: _, }, meta: _, } = update; @@ -195,7 +191,6 @@ impl From for ToolCallUpdate { locations: Some(locations), raw_input, raw_output, - meta: None, }, meta: None, } diff --git a/schema/schema.json b/schema/schema.json index 86dea572..77f8cfea 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -92,8 +92,8 @@ "title": "InitializeResponse" }, { - "title": "AuthenticateResponse", - "type": "null" + "$ref": "#/$defs/AuthenticateResponse", + "title": "AuthenticateResponse" }, { "$ref": "#/$defs/NewSessionResponse", @@ -208,6 +208,17 @@ "x-method": "authenticate", "x-side": "agent" }, + "AuthenticateResponse": { + "description": "Response to authenticate method", + "properties": { + "_meta": { + "description": "Extension point for implementations" + } + }, + "type": "object", + "x-method": "authenticate", + "x-side": "agent" + }, "AvailableCommand": { "description": "Information about a command.", "properties": { @@ -359,8 +370,8 @@ "ClientResponse": { "anyOf": [ { - "title": "WriteTextFileResponse", - "type": "null" + "$ref": "#/$defs/WriteTextFileResponse", + "title": "WriteTextFileResponse" }, { "$ref": "#/$defs/ReadTextFileResponse", @@ -379,16 +390,16 @@ "title": "TerminalOutputResponse" }, { - "title": "ReleaseTerminalResponse", - "type": "null" + "$ref": "#/$defs/ReleaseTerminalResponse", + "title": "ReleaseTerminalResponse" }, { "$ref": "#/$defs/WaitForTerminalExitResponse", "title": "WaitForTerminalExitResponse" }, { - "title": "KillTerminalResponse", - "type": "null" + "$ref": "#/$defs/KillTerminalCommandResponse", + "title": "KillTerminalResponse" }, { "title": "ExtMethodResponse" @@ -829,6 +840,17 @@ "x-method": "terminal/kill", "x-side": "client" }, + "KillTerminalCommandResponse": { + "description": "Response to terminal/kill command method", + "properties": { + "_meta": { + "description": "Extension point for implementations" + } + }, + "type": "object", + "x-method": "terminal/kill", + "x-side": "client" + }, "LoadSessionRequest": { "description": "Request parameters for loading an existing session.\n\nOnly available if the Agent supports the `loadSession` capability.\n\nSee protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/session-setup#loading-sessions)", "properties": { @@ -874,7 +896,9 @@ "description": "**UNSTABLE**\n\nThis field is not part of the spec, and may be removed or changed at any point." } }, - "type": "object" + "type": "object", + "x-method": "session/load", + "x-side": "agent" }, "McpCapabilities": { "description": "MCP capabilities supported by the agent", @@ -1275,7 +1299,9 @@ } }, "required": ["content"], - "type": "object" + "type": "object", + "x-method": "fs/read_text_file", + "x-side": "client" }, "ReleaseTerminalRequest": { "description": "Request to release a terminal and free its resources.", @@ -1297,6 +1323,17 @@ "x-method": "terminal/release", "x-side": "client" }, + "ReleaseTerminalResponse": { + "description": "Response to terminal/release method", + "properties": { + "_meta": { + "description": "Extension point for implementations" + } + }, + "type": "object", + "x-method": "terminal/release", + "x-side": "client" + }, "RequestPermissionOutcome": { "description": "The outcome of a permission request.", "oneOf": [ @@ -1715,6 +1752,9 @@ }, "SetSessionModeResponse": { "description": "**UNSTABLE**\n\nThis type is not part of the spec, and may be removed or changed at any point.", + "properties": { + "meta": true + }, "type": "object" }, "StopReason": { @@ -2193,6 +2233,17 @@ "type": "object", "x-method": "fs/write_text_file", "x-side": "client" + }, + "WriteTextFileResponse": { + "description": "Response to fs/write_text_file", + "properties": { + "_meta": { + "description": "Extension point for implementations" + } + }, + "type": "object", + "x-method": "fs/write_text_file", + "x-side": "client" } }, "$schema": "https://json-schema.org/draft/2020-12/schema", diff --git a/typescript/acp.test.ts b/typescript/acp.test.ts index abe83767..d3c159a0 100644 --- a/typescript/acp.test.ts +++ b/typescript/acp.test.ts @@ -122,7 +122,7 @@ describe("Connection", () => { const currentCount = requestCount; await new Promise((resolve) => setTimeout(resolve, 40)); console.log(`Write request ${currentCount} completed`); - return null; + return {}; } async readTextFile( params: ReadTextFileRequest, @@ -208,9 +208,9 @@ describe("Connection", () => { // Verify all requests completed successfully expect(results).toHaveLength(3); - expect(results[0]).toBeNull(); - expect(results[1]).toBeNull(); - expect(results[2]).toBeNull(); + expect(results[0]).toEqual({}); + expect(results[1]).toEqual({}); + expect(results[2]).toEqual({}); expect(requestCount).toBe(3); }); @@ -223,7 +223,7 @@ describe("Connection", () => { params: WriteTextFileRequest, ): Promise { messageLog.push(`writeTextFile called: ${params.path}`); - return null; + return {}; } async readTextFile( params: ReadTextFileRequest, @@ -357,7 +357,7 @@ describe("Connection", () => { async writeTextFile( _: WriteTextFileRequest, ): Promise { - return null; + return {}; } async readTextFile( _: ReadTextFileRequest, @@ -462,7 +462,7 @@ describe("Connection", () => { async writeTextFile( _: WriteTextFileRequest, ): Promise { - return null; + return {}; } async readTextFile( _: ReadTextFileRequest, @@ -553,7 +553,7 @@ describe("Connection", () => { async writeTextFile( _: WriteTextFileRequest, ): Promise { - return null; + return {}; } async readTextFile( _: ReadTextFileRequest, @@ -683,7 +683,7 @@ describe("Connection", () => { async writeTextFile( _: WriteTextFileRequest, ): Promise { - return null; + return {}; } async readTextFile( _: ReadTextFileRequest, @@ -766,4 +766,173 @@ describe("Connection", () => { info: "test", }); }); + + it("handles methods returning response objects with _meta or void", async () => { + // Create client that returns both response objects and void + class TestClient implements Client { + async writeTextFile( + params: WriteTextFileRequest, + ): Promise { + // Return response object with _meta + return { + _meta: { + timestamp: new Date().toISOString(), + version: "1.0.0", + }, + }; + } + async readTextFile( + params: ReadTextFileRequest, + ): Promise { + return { + content: "test content", + _meta: { + encoding: "utf-8", + }, + }; + } + async requestPermission( + params: RequestPermissionRequest, + ): Promise { + return { + outcome: { + outcome: "selected", + optionId: "allow", + }, + _meta: { + userId: "test-user", + }, + }; + } + async sessionUpdate(params: SessionNotification): Promise { + // Returns void + } + } + + // Create agent that returns both response objects and void + class TestAgent implements Agent { + async initialize(params: InitializeRequest): Promise { + return { + protocolVersion: params.protocolVersion, + agentCapabilities: { loadSession: true }, + _meta: { + agentVersion: "2.0.0", + }, + }; + } + async newSession(params: NewSessionRequest): Promise { + return { + sessionId: "test-session", + _meta: { + sessionType: "ephemeral", + }, + }; + } + async loadSession( + params: LoadSessionRequest, + ): Promise { + // Test returning minimal response + return {}; + } + async authenticate( + params: AuthenticateRequest, + ): Promise { + if (params.methodId === "none") { + // Test returning void + return; + } + // Test returning response with _meta + return { + _meta: { + authenticated: true, + method: params.methodId, + }, + }; + } + async prompt(params: PromptRequest): Promise { + return { stopReason: "end_turn" }; + } + async cancel(params: CancelNotification): Promise { + // Returns void + } + } + + const agentConnection = new ClientSideConnection( + () => new TestClient(), + clientToAgent.writable, + agentToClient.readable, + ); + + const clientConnection = new AgentSideConnection( + () => new TestAgent(), + agentToClient.writable, + clientToAgent.readable, + ); + + // Test writeTextFile returns response with _meta + const writeResponse = await clientConnection.writeTextFile({ + path: "/test.txt", + content: "test", + sessionId: "test-session", + }); + expect(writeResponse).toEqual({ + _meta: { + timestamp: expect.any(String), + version: "1.0.0", + }, + }); + + // Test readTextFile returns response with content and _meta + const readResponse = await clientConnection.readTextFile({ + path: "/test.txt", + sessionId: "test-session", + }); + expect(readResponse.content).toBe("test content"); + expect(readResponse._meta).toEqual({ + encoding: "utf-8", + }); + + // Test initialize with _meta + const initResponse = await agentConnection.initialize({ + protocolVersion: PROTOCOL_VERSION, + clientCapabilities: {}, + }); + expect(initResponse._meta).toEqual({ + agentVersion: "2.0.0", + }); + + // Test authenticate returning void + const authResponseVoid = await agentConnection.authenticate({ + methodId: "none", + }); + expect(authResponseVoid).toEqual({}); + + // Test authenticate returning response with _meta + const authResponse = await agentConnection.authenticate({ + methodId: "oauth", + }); + expect(authResponse).toEqual({ + _meta: { + authenticated: true, + method: "oauth", + }, + }); + + // Test newSession with _meta + const sessionResponse = await agentConnection.newSession({ + cwd: "/test", + mcpServers: [], + }); + expect(sessionResponse._meta).toEqual({ + sessionType: "ephemeral", + }); + + // Test loadSession returning minimal response + const loadResponse = await agentConnection.loadSession({ + sessionId: "test-session", + mcpServers: [], + cwd: "/test", + }); + expect(loadResponse).toEqual({}); + }); }); diff --git a/typescript/acp.ts b/typescript/acp.ts index 39c5bc35..4a7ffcda 100644 --- a/typescript/acp.ts +++ b/typescript/acp.ts @@ -62,16 +62,18 @@ export class AgentSideConnection { } const validatedParams = schema.setSessionModeRequestSchema.parse(params); - return agent.setSessionMode( + const result = await agent.setSessionMode( validatedParams as schema.SetSessionModeRequest, ); + return result ?? {}; } case schema.AGENT_METHODS.authenticate: { const validatedParams = schema.authenticateRequestSchema.parse(params); - return agent.authenticate( + const result = await agent.authenticate( validatedParams as schema.AuthenticateRequest, ); + return result ?? {}; } case schema.AGENT_METHODS.session_prompt: { const validatedParams = schema.promptRequestSchema.parse(params); @@ -191,9 +193,11 @@ export class AgentSideConnection { async writeTextFile( params: schema.WriteTextFileRequest, ): Promise { - return await this.#connection.sendRequest( - schema.CLIENT_METHODS.fs_write_text_file, - params, + return ( + (await this.#connection.sendRequest( + schema.CLIENT_METHODS.fs_write_text_file, + params, + )) ?? {} ); } @@ -314,13 +318,12 @@ export class TerminalHandle { * * Useful for implementing timeouts or cancellation. */ - async kill(): Promise { - return await this.#connection.sendRequest( - schema.CLIENT_METHODS.terminal_kill, - { + async kill(): Promise { + return ( + (await this.#connection.sendRequest(schema.CLIENT_METHODS.terminal_kill, { sessionId: this.#sessionId, terminalId: this.id, - }, + })) ?? {} ); } @@ -336,11 +339,16 @@ export class TerminalHandle { * * **Important:** Always call this method when done with the terminal. */ - async release(): Promise { - await this.#connection.sendRequest(schema.CLIENT_METHODS.terminal_release, { - sessionId: this.#sessionId, - terminalId: this.id, - }); + async release(): Promise { + return ( + (await this.#connection.sendRequest( + schema.CLIENT_METHODS.terminal_release, + { + sessionId: this.#sessionId, + terminalId: this.id, + }, + )) ?? {} + ); } async [Symbol.asyncDispose](): Promise { @@ -423,9 +431,10 @@ export class ClientSideConnection implements Agent { case schema.CLIENT_METHODS.terminal_release: { const validatedParams = schema.releaseTerminalRequestSchema.parse(params); - return client.releaseTerminal?.( + const result = await client.releaseTerminal?.( validatedParams as schema.ReleaseTerminalRequest, ); + return result ?? {}; } case schema.CLIENT_METHODS.terminal_wait_for_exit: { const validatedParams = @@ -437,9 +446,10 @@ export class ClientSideConnection implements Agent { case schema.CLIENT_METHODS.terminal_kill: { const validatedParams = schema.killTerminalCommandRequestSchema.parse(params); - return client.killTerminal?.( + const result = await client.killTerminal?.( validatedParams as schema.KillTerminalCommandRequest, ); + return result ?? {}; } default: // Handle extension methods (any method starting with '_') @@ -552,9 +562,11 @@ export class ClientSideConnection implements Agent { async loadSession( params: schema.LoadSessionRequest, ): Promise { - return await this.#connection.sendRequest( - schema.AGENT_METHODS.session_load, - params, + return ( + (await this.#connection.sendRequest( + schema.AGENT_METHODS.session_load, + params, + )) ?? {} ); } @@ -568,10 +580,12 @@ export class ClientSideConnection implements Agent { */ async setSessionMode( params: schema.SetSessionModeRequest, - ): Promise { - return await this.#connection.sendRequest( - schema.AGENT_METHODS.session_set_mode, - params, + ): Promise { + return ( + (await this.#connection.sendRequest( + schema.AGENT_METHODS.session_set_mode, + params, + )) ?? {} ); } @@ -586,10 +600,14 @@ export class ClientSideConnection implements Agent { * * See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization) */ - async authenticate(params: schema.AuthenticateRequest): Promise { - return await this.#connection.sendRequest( - schema.AGENT_METHODS.authenticate, - params, + async authenticate( + params: schema.AuthenticateRequest, + ): Promise { + return ( + (await this.#connection.sendRequest( + schema.AGENT_METHODS.authenticate, + params, + )) ?? {} ); } @@ -1109,7 +1127,9 @@ export interface Client { * * @see {@link https://agentclientprotocol.com/protocol/terminals#releasing-terminals | Releasing Terminals} */ - releaseTerminal?(params: schema.ReleaseTerminalRequest): Promise; + releaseTerminal?( + params: schema.ReleaseTerminalRequest, + ): Promise; /** * Waits for a terminal command to exit and returns its exit status. @@ -1136,7 +1156,9 @@ export interface Client { * * @see {@link https://agentclientprotocol.com/protocol/terminals#killing-commands | Killing Commands} */ - killTerminal?(params: schema.KillTerminalCommandRequest): Promise; + killTerminal?( + params: schema.KillTerminalCommandRequest, + ): Promise; /** * Extension method @@ -1223,7 +1245,7 @@ export interface Agent { */ setSessionMode?( params: schema.SetSessionModeRequest, - ): Promise; + ): Promise; /** * Authenticates the client using the specified authentication method. * @@ -1235,7 +1257,9 @@ export interface Agent { * * See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization) */ - authenticate(params: schema.AuthenticateRequest): Promise; + authenticate( + params: schema.AuthenticateRequest, + ): Promise; /** * Processes a user prompt within a session. * diff --git a/typescript/examples/agent.ts b/typescript/examples/agent.ts index 2d243391..fdecacc9 100644 --- a/typescript/examples/agent.ts +++ b/typescript/examples/agent.ts @@ -42,8 +42,11 @@ class ExampleAgent implements Agent { }; } - async authenticate(params: schema.AuthenticateRequest): Promise { - // No auth needed + async authenticate( + params: schema.AuthenticateRequest, + ): Promise { + // No auth needed - return empty response + return {}; } async setSessionMode( diff --git a/typescript/examples/client.ts b/typescript/examples/client.ts index 32069e39..f135fe0d 100644 --- a/typescript/examples/client.ts +++ b/typescript/examples/client.ts @@ -77,7 +77,7 @@ class ExampleClient implements acp.Client { JSON.stringify(params, null, 2), ); - return null; + return {}; } async readTextFile( diff --git a/typescript/schema.ts b/typescript/schema.ts index 6809f055..480a2bc3 100644 --- a/typescript/schema.ts +++ b/typescript/schema.ts @@ -223,9 +223,6 @@ export type ClientResponse = | WaitForTerminalExitResponse | KillTerminalResponse | ExtMethodResponse; -export type WriteTextFileResponse = null; -export type ReleaseTerminalResponse = null; -export type KillTerminalResponse = null; /** * All possible notifications that a client can send to an agent. * @@ -414,7 +411,6 @@ export type AgentResponse = | SetSessionModeResponse | PromptResponse | ExtMethodResponse1; -export type AuthenticateResponse = null; /** * All possible notifications that an agent can send to a client. * @@ -781,6 +777,17 @@ export interface KillTerminalCommandRequest { export interface ExtMethodRequest { [k: string]: unknown; } +/** + * Response to fs/write_text_file + */ +export interface WriteTextFileResponse { + /** + * Extension point for implementations + */ + _meta?: { + [k: string]: unknown; + }; +} /** * Response containing the contents of a text file. */ @@ -875,6 +882,17 @@ export interface TerminalExitStatus { */ signal?: string | null; } +/** + * Response to terminal/release method + */ +export interface ReleaseTerminalResponse { + /** + * Extension point for implementations + */ + _meta?: { + [k: string]: unknown; + }; +} /** * Response containing the exit status of a terminal command. */ @@ -894,6 +912,17 @@ export interface WaitForTerminalExitResponse { */ signal?: string | null; } +/** + * Response to terminal/kill command method + */ +export interface KillTerminalResponse { + /** + * Extension point for implementations + */ + _meta?: { + [k: string]: unknown; + }; +} export interface ExtMethodResponse { [k: string]: unknown; } @@ -1260,6 +1289,17 @@ export interface AuthMethod { */ name: string; } +/** + * Response to authenticate method + */ +export interface AuthenticateResponse { + /** + * Extension point for implementations + */ + _meta?: { + [k: string]: unknown; + }; +} /** * Response from creating a new session. * @@ -1338,7 +1378,9 @@ export interface LoadSessionResponse { * * This type is not part of the spec, and may be removed or changed at any point. */ -export interface SetSessionModeResponse {} +export interface SetSessionModeResponse { + meta?: unknown; +} /** * Response from processing a user prompt. * @@ -1679,7 +1721,9 @@ export const toolCallStatusSchema = z.union([ ]); /** @internal */ -export const writeTextFileResponseSchema = z.null(); +export const writeTextFileResponseSchema = z.object({ + _meta: z.record(z.unknown()).optional(), +}); /** @internal */ export const readTextFileResponseSchema = z.object({ @@ -1708,7 +1752,9 @@ export const createTerminalResponseSchema = z.object({ }); /** @internal */ -export const releaseTerminalResponseSchema = z.null(); +export const releaseTerminalResponseSchema = z.object({ + _meta: z.record(z.unknown()).optional(), +}); /** @internal */ export const waitForTerminalExitResponseSchema = z.object({ @@ -1718,7 +1764,9 @@ export const waitForTerminalExitResponseSchema = z.object({ }); /** @internal */ -export const killTerminalResponseSchema = z.null(); +export const killTerminalResponseSchema = z.object({ + _meta: z.record(z.unknown()).optional(), +}); /** @internal */ export const extMethodResponseSchema = z.record(z.unknown()); @@ -1769,10 +1817,14 @@ export const embeddedResourceResourceSchema = z.union([ ]); /** @internal */ -export const authenticateResponseSchema = z.null(); +export const authenticateResponseSchema = z.object({ + _meta: z.record(z.unknown()).optional(), +}); /** @internal */ -export const setSessionModeResponseSchema = z.object({}); +export const setSessionModeResponseSchema = z.object({ + meta: z.unknown().optional(), +}); /** @internal */ export const promptResponseSchema = z.object({