Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.4.8 (2025-10-16)

- Export `acp::Result<T, E = acp::Error>` for easier indication of ACP errors.

## 0.4.7 (2025-10-13)

- Depend on `agent-client-protocol-schema` for schema types
Expand Down
7 changes: 3 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "agent-client-protocol"
authors = ["Zed <hi@zed.dev>"]
version = "0.4.7"
version = "0.4.8"
edition = "2024"
license = "Apache-2.0"
description = "A protocol for standardizing communication between code editors and AI coding agents"
Expand All @@ -17,14 +17,13 @@ include = ["/src/**/*.rs", "/README.md", "/LICENSE", "/Cargo.toml"]
unstable = ["agent-client-protocol-schema/unstable"]

[dependencies]
agent-client-protocol-schema = "0.4.9"
agent-client-protocol-schema = "0.4.10"
anyhow = "1"
async-broadcast = "0.7"
async-trait = "0.1"
futures = { version = "0.3" }
log = "0.4"
parking_lot = "0.12"
schemars = { version = "1" }
serde = { version = "1", features = ["derive", "rc"] }
serde_json = { version = "1", features = ["raw_value"] }

Expand Down
2 changes: 1 addition & 1 deletion examples/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl acp::Agent for ExampleAgent {
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
async fn main() -> acp::Result<()> {
env_logger::init();

let outgoing = tokio::io::stdout().compat_write();
Expand Down
23 changes: 11 additions & 12 deletions examples/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
//! ```

use agent_client_protocol::{self as acp, Agent as _};
use anyhow::bail;
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};

struct ExampleClient {}
Expand All @@ -23,21 +22,21 @@ impl acp::Client for ExampleClient {
async fn request_permission(
&self,
_args: acp::RequestPermissionRequest,
) -> anyhow::Result<acp::RequestPermissionResponse, acp::Error> {
) -> acp::Result<acp::RequestPermissionResponse> {
Err(acp::Error::method_not_found())
}

async fn write_text_file(
&self,
_args: acp::WriteTextFileRequest,
) -> anyhow::Result<acp::WriteTextFileResponse, acp::Error> {
) -> acp::Result<acp::WriteTextFileResponse> {
Err(acp::Error::method_not_found())
}

async fn read_text_file(
&self,
_args: acp::ReadTextFileRequest,
) -> anyhow::Result<acp::ReadTextFileResponse, acp::Error> {
) -> acp::Result<acp::ReadTextFileResponse> {
Err(acp::Error::method_not_found())
}

Expand All @@ -51,35 +50,35 @@ impl acp::Client for ExampleClient {
async fn terminal_output(
&self,
_args: acp::TerminalOutputRequest,
) -> anyhow::Result<acp::TerminalOutputResponse, acp::Error> {
) -> acp::Result<acp::TerminalOutputResponse> {
Err(acp::Error::method_not_found())
}

async fn release_terminal(
&self,
_args: acp::ReleaseTerminalRequest,
) -> anyhow::Result<acp::ReleaseTerminalResponse, acp::Error> {
) -> acp::Result<acp::ReleaseTerminalResponse> {
Err(acp::Error::method_not_found())
}

async fn wait_for_terminal_exit(
&self,
_args: acp::WaitForTerminalExitRequest,
) -> anyhow::Result<acp::WaitForTerminalExitResponse, acp::Error> {
) -> acp::Result<acp::WaitForTerminalExitResponse> {
Err(acp::Error::method_not_found())
}

async fn kill_terminal_command(
&self,
_args: acp::KillTerminalCommandRequest,
) -> anyhow::Result<acp::KillTerminalCommandResponse, acp::Error> {
) -> acp::Result<acp::KillTerminalCommandResponse> {
Err(acp::Error::method_not_found())
}

async fn session_notification(
&self,
args: acp::SessionNotification,
) -> anyhow::Result<(), acp::Error> {
) -> acp::Result<(), acp::Error> {
match args.update {
acp::SessionUpdate::AgentMessageChunk { content } => {
let text = match content {
Expand All @@ -102,11 +101,11 @@ impl acp::Client for ExampleClient {
Ok(())
}

async fn ext_method(&self, _args: acp::ExtRequest) -> Result<acp::ExtResponse, acp::Error> {
async fn ext_method(&self, _args: acp::ExtRequest) -> acp::Result<acp::ExtResponse> {
Err(acp::Error::method_not_found())
}

async fn ext_notification(&self, _args: acp::ExtNotification) -> Result<(), acp::Error> {
async fn ext_notification(&self, _args: acp::ExtNotification) -> acp::Result<()> {
Err(acp::Error::method_not_found())
}
}
Expand All @@ -130,7 +129,7 @@ async fn main() -> anyhow::Result<()> {
child,
)
}
_ => bail!("Usage: client AGENT_PROGRAM AGENT_ARG..."),
_ => anyhow::bail!("Usage: client AGENT_PROGRAM AGENT_ARG..."),
};

// The ClientSideConnection will spawn futures onto our Tokio runtime.
Expand Down
65 changes: 32 additions & 33 deletions src/agent.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use std::{rc::Rc, sync::Arc};

use serde_json::value::RawValue;

use agent_client_protocol_schema::{
AuthenticateRequest, AuthenticateResponse, CancelNotification, Error, ExtNotification,
ExtRequest, ExtResponse, InitializeRequest, InitializeResponse, LoadSessionRequest,
LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse,
SetSessionModeRequest, SetSessionModeResponse,
Result, SetSessionModeRequest, SetSessionModeResponse,
};
#[cfg(feature = "unstable")]
use agent_client_protocol_schema::{SetSessionModelRequest, SetSessionModelResponse};
use serde_json::value::RawValue;

/// Defines the interface that all ACP-compliant agents must implement.
///
Expand All @@ -27,7 +26,7 @@ pub trait Agent {
/// The agent should respond with its supported protocol version and capabilities.
///
/// See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization)
async fn initialize(&self, args: InitializeRequest) -> Result<InitializeResponse, Error>;
async fn initialize(&self, args: InitializeRequest) -> Result<InitializeResponse>;

/// Authenticates the client using the specified authentication method.
///
Expand All @@ -38,7 +37,7 @@ pub trait Agent {
/// `new_session` without receiving an `auth_required` error.
///
/// See protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization)
async fn authenticate(&self, args: AuthenticateRequest) -> Result<AuthenticateResponse, Error>;
async fn authenticate(&self, args: AuthenticateRequest) -> Result<AuthenticateResponse>;

/// Creates a new conversation session with the agent.
///
Expand All @@ -52,7 +51,7 @@ pub trait Agent {
/// May return an `auth_required` error if the agent requires authentication.
///
/// See protocol docs: [Session Setup](https://agentclientprotocol.com/protocol/session-setup)
async fn new_session(&self, args: NewSessionRequest) -> Result<NewSessionResponse, Error>;
async fn new_session(&self, args: NewSessionRequest) -> Result<NewSessionResponse>;

/// Processes a user prompt within a session.
///
Expand All @@ -65,7 +64,7 @@ pub trait Agent {
/// - Returns when the turn is complete with a stop reason
///
/// See protocol docs: [Prompt Turn](https://agentclientprotocol.com/protocol/prompt-turn)
async fn prompt(&self, args: PromptRequest) -> Result<PromptResponse, Error>;
async fn prompt(&self, args: PromptRequest) -> Result<PromptResponse>;

/// Cancels ongoing operations for a session.
///
Expand All @@ -78,7 +77,7 @@ pub trait Agent {
/// - Respond to the original `session/prompt` request with `StopReason::Cancelled`
///
/// See protocol docs: [Cancellation](https://agentclientprotocol.com/protocol/prompt-turn#cancellation)
async fn cancel(&self, args: CancelNotification) -> Result<(), Error>;
async fn cancel(&self, args: CancelNotification) -> Result<()>;

/// Loads an existing session to resume a previous conversation.
///
Expand All @@ -90,7 +89,7 @@ pub trait Agent {
/// - Stream the entire conversation history back to the client via notifications
///
/// See protocol docs: [Loading Sessions](https://agentclientprotocol.com/protocol/session-setup#loading-sessions)
async fn load_session(&self, _args: LoadSessionRequest) -> Result<LoadSessionResponse, Error> {
async fn load_session(&self, _args: LoadSessionRequest) -> Result<LoadSessionResponse> {
Err(Error::method_not_found())
}

Expand All @@ -110,7 +109,7 @@ pub trait Agent {
async fn set_session_mode(
&self,
_args: SetSessionModeRequest,
) -> Result<SetSessionModeResponse, Error> {
) -> Result<SetSessionModeResponse> {
Err(Error::method_not_found())
}

Expand All @@ -123,7 +122,7 @@ pub trait Agent {
async fn set_session_model(
&self,
_args: SetSessionModelRequest,
) -> Result<SetSessionModelResponse, Error> {
) -> Result<SetSessionModelResponse> {
Err(Error::method_not_found())
}

Expand All @@ -133,7 +132,7 @@ pub trait Agent {
/// protocol compatibility.
///
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
async fn ext_method(&self, _args: ExtRequest) -> Result<ExtResponse, Error> {
async fn ext_method(&self, _args: ExtRequest) -> Result<ExtResponse> {
Ok(RawValue::NULL.to_owned().into())
}

Expand All @@ -143,89 +142,89 @@ pub trait Agent {
/// while maintaining protocol compatibility.
///
/// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
async fn ext_notification(&self, _args: ExtNotification) -> Result<(), Error> {
async fn ext_notification(&self, _args: ExtNotification) -> Result<()> {
Ok(())
}
}

#[async_trait::async_trait(?Send)]
impl<T: Agent> Agent for Rc<T> {
async fn initialize(&self, args: InitializeRequest) -> Result<InitializeResponse, Error> {
async fn initialize(&self, args: InitializeRequest) -> Result<InitializeResponse> {
self.as_ref().initialize(args).await
}
async fn authenticate(&self, args: AuthenticateRequest) -> Result<AuthenticateResponse, Error> {
async fn authenticate(&self, args: AuthenticateRequest) -> Result<AuthenticateResponse> {
self.as_ref().authenticate(args).await
}
async fn new_session(&self, args: NewSessionRequest) -> Result<NewSessionResponse, Error> {
async fn new_session(&self, args: NewSessionRequest) -> Result<NewSessionResponse> {
self.as_ref().new_session(args).await
}
async fn load_session(&self, args: LoadSessionRequest) -> Result<LoadSessionResponse, Error> {
async fn load_session(&self, args: LoadSessionRequest) -> Result<LoadSessionResponse> {
self.as_ref().load_session(args).await
}
async fn set_session_mode(
&self,
args: SetSessionModeRequest,
) -> Result<SetSessionModeResponse, Error> {
) -> Result<SetSessionModeResponse> {
self.as_ref().set_session_mode(args).await
}
async fn prompt(&self, args: PromptRequest) -> Result<PromptResponse, Error> {
async fn prompt(&self, args: PromptRequest) -> Result<PromptResponse> {
self.as_ref().prompt(args).await
}
async fn cancel(&self, args: CancelNotification) -> Result<(), Error> {
async fn cancel(&self, args: CancelNotification) -> Result<()> {
self.as_ref().cancel(args).await
}
#[cfg(feature = "unstable")]
async fn set_session_model(
&self,
args: SetSessionModelRequest,
) -> Result<SetSessionModelResponse, Error> {
) -> Result<SetSessionModelResponse> {
self.as_ref().set_session_model(args).await
}
async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error> {
async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse> {
self.as_ref().ext_method(args).await
}
async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error> {
async fn ext_notification(&self, args: ExtNotification) -> Result<()> {
self.as_ref().ext_notification(args).await
}
}

#[async_trait::async_trait(?Send)]
impl<T: Agent> Agent for Arc<T> {
async fn initialize(&self, args: InitializeRequest) -> Result<InitializeResponse, Error> {
async fn initialize(&self, args: InitializeRequest) -> Result<InitializeResponse> {
self.as_ref().initialize(args).await
}
async fn authenticate(&self, args: AuthenticateRequest) -> Result<AuthenticateResponse, Error> {
async fn authenticate(&self, args: AuthenticateRequest) -> Result<AuthenticateResponse> {
self.as_ref().authenticate(args).await
}
async fn new_session(&self, args: NewSessionRequest) -> Result<NewSessionResponse, Error> {
async fn new_session(&self, args: NewSessionRequest) -> Result<NewSessionResponse> {
self.as_ref().new_session(args).await
}
async fn load_session(&self, args: LoadSessionRequest) -> Result<LoadSessionResponse, Error> {
async fn load_session(&self, args: LoadSessionRequest) -> Result<LoadSessionResponse> {
self.as_ref().load_session(args).await
}
async fn set_session_mode(
&self,
args: SetSessionModeRequest,
) -> Result<SetSessionModeResponse, Error> {
) -> Result<SetSessionModeResponse> {
self.as_ref().set_session_mode(args).await
}
async fn prompt(&self, args: PromptRequest) -> Result<PromptResponse, Error> {
async fn prompt(&self, args: PromptRequest) -> Result<PromptResponse> {
self.as_ref().prompt(args).await
}
async fn cancel(&self, args: CancelNotification) -> Result<(), Error> {
async fn cancel(&self, args: CancelNotification) -> Result<()> {
self.as_ref().cancel(args).await
}
#[cfg(feature = "unstable")]
async fn set_session_model(
&self,
args: SetSessionModelRequest,
) -> Result<SetSessionModelResponse, Error> {
) -> Result<SetSessionModelResponse> {
self.as_ref().set_session_model(args).await
}
async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse, Error> {
async fn ext_method(&self, args: ExtRequest) -> Result<ExtResponse> {
self.as_ref().ext_method(args).await
}
async fn ext_notification(&self, args: ExtNotification) -> Result<(), Error> {
async fn ext_notification(&self, args: ExtNotification) -> Result<()> {
self.as_ref().ext_notification(args).await
}
}
Loading