Crate sacp

Crate sacp 

Source
Expand description

§sacp – the Symposium Agent Client Protocol (ACP) SDK

sacp is a Rust SDK for building agents and editors using the Agent-Client Protocol (ACP). It makes it easy to build ACP editors and clients – or, indeed, any JSON-RPC-based application.

§Quick Start

Building an ACP agent is straightforward with sacp’s type-safe API:

use sacp::role::UntypedRole;
use sacp::{MessageCx, UntypedMessage};
use sacp::schema::{InitializeRequest, InitializeResponse, AgentCapabilities};
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};

// Start by creating an agent connection
UntypedRole::builder()
.name("my-agent") // Give it a name for logging purposes
.on_receive_request(async move |initialize: InitializeRequest, request_cx, _cx| {
    // Create one or more request handlers -- these are attempted in order.
    // You can do anything you want in here, but you should eventually
    // respond to the request with `request_cx.respond(...)`:
    request_cx.respond(InitializeResponse {
        protocol_version: initialize.protocol_version,
        agent_capabilities: AgentCapabilities::default(),
        auth_methods: Default::default(),
        agent_info: Default::default(),
        meta: Default::default(),
    })
})
.on_receive_message(async move |message: MessageCx, cx| {
    // You can also handle any kind of message:
    message.respond_with_error(sacp::util::internal_error("TODO"), cx)
})
.serve(sacp::ByteStreams::new(
    tokio::io::stdout().compat_write(),
    tokio::io::stdin().compat(),
))
.await

§Common Patterns

§Pattern 1: Defining Reusable Components

When building agents or proxies, define a struct that implements Component. Internally, use the role’s builder() method to set up handlers:

use sacp::Component;

struct MyAgent {
    config: AgentConfig,
}

impl Component for MyAgent {
    async fn serve(self, client: impl Component) -> Result<(), sacp::Error> {
        UntypedRole::builder()
            .name("my-agent")
            .on_receive_request(async move |req: PromptRequest, cx| {
                // Don't block the message loop! Use await_when_* for async work
                cx.respond(self.process_prompt(req))
                    .await_when_result_received(async move |response| {
                        // This runs after the response is received
                        log_response(&response);
                        cx.respond(response)
                    })
            })
            .serve(client)
            .await
    }
}

Important: Message handlers run on the event loop. Blocking in a handler will prevent the connection from processing new messages. Use JrConnectionCx::spawn to offload expensive work, or use the await_when_* methods to avoid blocking.

§Pattern 2: Custom Message Handlers

For reusable message handling logic, implement JrMessageHandler and use MatchMessage for dispatching:

use sacp::{JrMessageHandler, MessageAndCx, Handled};
use sacp::util::MatchMessage;

struct MyHandler {
    state: Arc<Mutex<State>>,
}

impl JrMessageHandler for MyHandler {
    async fn handle_message(&mut self, message: MessageAndCx)
        -> Result<Handled<MessageAndCx>, sacp::Error>
    {
        MatchMessage::new(message)
            .if_request(async |req: MyRequest, cx| {
                // Handle using self.state
                cx.respond(MyResponse { /* ... */ })
            })
            .await
            .done()
    }

    fn describe_chain(&self) -> impl std::fmt::Debug { "MyHandler" }
}

§Pattern 3: Connecting as a Client

To connect to a JSON-RPC server and send requests, use with_client. Note the use of async (not async move) to share access to local variables:

UntypedRole::builder()
    .on_receive_notification(async |notif: SessionUpdate, cx| {
        // Handle notifications from the server
        Ok(())
    })
    .with_client(sacp::ByteStreams::new(stdout, stdin), async |cx| {
        // Send requests using the connection context
        let response = cx.send_request(MyRequest { /* ... */ })
            .block_task()
            .await?;

        // Can access local variables here
        process_response(response);

        Ok(())
    })
    .await

§Using the Request Context

The request context (JrRequestCx) provided to handlers is not just for responding—it offers several capabilities:

  • Respond to the request: cx.respond(response) sends a response back to the caller
  • Send requests to the other side: cx.send_request(request) initiates a new request
  • Send notifications: cx.send_notification(notification) sends a fire-and-forget message
  • Spawn background tasks: cx.spawn(future) runs work concurrently without blocking the message loop

If you need a connection context that’s independent of a particular request (for example, to store in a struct for later use), use cx.connection_cx(). This gives you a JrConnectionCx that can spawn tasks and send messages but isn’t tied to responding to a specific request.

§Learning more

You can learn more in the docs for JrConnection or on our GitHub Pages site.

You may also enjoy looking at some of these examples:

  • simple_agent.rs - Minimal agent implementation
  • yolo_one_shot_client.rs - Complete client that spawns an agent and sends a prompt
  • elizacp - Full working agent with session management (also useful for testing)
  • sacp-conductor - The “conductor” is an ACP agent that composes proxy components with a final agent.
  • sacp-proxy - Framework for building ACP proxies that extend agent behavior
  • sacp-tokio - Tokio-specific utilities (process spawning, connection management)
  • sacp-conductor - Binary for orchestrating proxy chains

Re-exports§

pub use role::Agent;
pub use role::AgentToClient;
pub use role::Client;
pub use role::ClientToAgent;
pub use role::Conductor;
pub use role::HasDefaultEndpoint;
pub use role::HasEndpoint;
pub use role::JrEndpoint;
pub use role::JrRole;
pub use role::ProxyToConductor;
pub use component::Component;
pub use component::DynComponent;

Modules§

component
Component abstraction for agents and proxies Component abstraction for agents and proxies.
handler
JSON-RPC handler types for building custom message handlers Handler types for building custom JSON-RPC message handlers.
jsonrpcmsg
JSON-RPC message types.
mcp
MCP declarations (minimal)
mcp_server
MCP server support for providing MCP tools over ACP MCP server support for providing MCP tools over ACP.
role
Proxy support for building ACP proxy components Role types for JSON-RPC connections Role traits and Role types for ACP and related protocols.
schema
ACP protocol schema types - all message types, requests, responses, and supporting types ACP protocol schema types and message implementations.
util
Utility functions and types

Structs§

ByteStreams
A component that communicates over byte streams (stdin/stdout, sockets, pipes, etc.).
Channel
A channel endpoint representing one side of a bidirectional message channel.
Error
JSON-RPC error object.
ErrorCode
Predefined error codes for common JSON-RPC and ACP-specific errors.
JrConnection
A JSON-RPC connection with an active transport.
JrConnectionBuilder
A JSON-RPC connection that can act as either a server, client, or both.
JrConnectionCx
Trait for types that can provide transport for JSON-RPC messages.
JrRequestCx
The context to respond to an incoming request.
JrResponse
Represents a pending response of type R from an outgoing request.
Lines
A component that communicates over line streams.
McpAcpTransport
The mcp_acp_transport capability - indicates support for MCP-over-ACP bridging.
NullHandler
Null handler that accepts no messages.
UntypedMessage
An incoming JSON message without any typing. Can be a request or a notification.

Enums§

AgentNotification
All possible notifications that an agent can send to a client.
AgentRequest
All possible requests that an agent can send to a client.
AgentResponse
All possible responses that an agent can send to a client.
ClientNotification
All possible notifications that a client can send to an agent.
ClientRequest
All possible requests that a client can send to an agent.
ClientResponse
All possible responses that a client can send to an agent.
Handled
Return type from JrHandler; indicates whether the request was handled or not.
MessageCx
An enum capturing an in-flight request or notification. In the case of a request, also includes the context used to respond to the request.

Traits§

IntoHandled
Trait for converting handler return values into Handled.
JrMessage
Common bounds for any JSON-RPC message.
JrMessageHandler
Handlers are invoked when new messages arrive at the JrConnection. They have a chance to inspect the method and parameters and decide whether to “claim” the request (i.e., handle it). If they do not claim it, the request will be passed to the next handler.
JrMessageHandlerSend
A version of JrMessageHandler where the handle_message code is required to be Send; any type implementing this trait must also implement JrMessageHandler.
JrNotification
A struct that represents a notification (JSON-RPC message that does not expect a response).
JrRequest
A struct that represents a request (JSON-RPC message expecting a response).
JrResponsePayload
Defines the “payload” of a successful response to a JSON-RPC request.
MetaCapability
Trait for capabilities stored in the _meta.symposium object.
MetaCapabilityExt
Extension trait for checking and modifying capabilities in InitializeRequest.

Type Aliases§

BoxFuture
An owned dynamically typed Future for use in cases where you can’t statically type your result or need to add some indirection.

Derive Macros§

JrNotification
Derive macro for implementing JrNotification and JrMessage traits.
JrRequest
Derive macro for implementing JrRequest and JrMessage traits.
JrResponsePayload
Derive macro for implementing JrResponsePayload trait.