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
93 changes: 90 additions & 3 deletions md/protocol-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,93 @@ The SDK handles the `initialize` negotiation at the JSON-RPC boundary:
- After initialization, the local API version and negotiated wire version must
match. The SDK does not convert traffic between v1 and v2.

That means v1 and v2 implementations need separate handlers today. A future SDK
API can route traffic to a registered implementation for the negotiated protocol
version, but `Agent.v2()` and `Client.v2()` are currently v2-only.
That means v1 and v2 implementations still need separate handlers.
`Agent.v2()` and `Client.v2()` are v2-only. While protocol v2 stabilizes, the
`unstable_protocol_v2` crate feature also exposes `Agent.protocol_router()` and
`Client.protocol_router()` for composing version-specific implementations.

Agents can add protocol implementations independently, which makes it easy for
applications built with v2 support to control v2 rollout with a runtime feature
flag:

```rust
use agent_client_protocol::schema::{v1, v2};
use agent_client_protocol::{Agent, ConnectTo};

# fn implementation() -> v2::Implementation {
# v2::Implementation::new("example", "0.1.0")
# }
# async fn serve(client_transport: impl agent_client_protocol::ConnectTo<Agent>) -> agent_client_protocol::Result<()> {
# let enable_protocol_v2 = true;
let v1_agent = Agent.builder().on_receive_request(
async |initialize: v1::InitializeRequest, responder, _cx| {
responder.respond(v1::InitializeResponse::new(initialize.protocol_version))
},
agent_client_protocol::on_receive_request!(),
);

let agent = Agent.protocol_router().with_v1(v1_agent);

let agent = if enable_protocol_v2 {
let v2_agent = Agent.v2().on_receive_request(
async |initialize: v2::InitializeRequest, responder, _cx| {
responder.respond(v2::InitializeResponse::new(
initialize.protocol_version,
implementation(),
))
},
agent_client_protocol::on_receive_request!(),
);

agent.with_v2(v2_agent)
} else {
agent
};

agent
.connect_to(client_transport)
.await?;
# Ok(())
# }
```

The protocol router reads the initial `initialize` request, selects the
highest configured protocol version that is compatible with the requested
version, and then hands the connection to that implementation. If only v2 is
configured, v1 clients are rejected without changing the fluent API. The router
does not convert messages between v1 and v2 after routing.

Clients use the same fluent shape:

```rust
use agent_client_protocol::{Client, ConnectTo};

# fn v1_client() -> impl agent_client_protocol::ConnectTo<agent_client_protocol::Agent> {
# Client.builder()
# }
# fn v2_client() -> impl agent_client_protocol::ConnectTo<agent_client_protocol::Agent> {
# Client.v2()
# }
# async fn run(agent_transport: impl agent_client_protocol::ConnectTo<agent_client_protocol::Client>) -> agent_client_protocol::Result<()> {
# let enable_protocol_v2 = true;
let client = Client.protocol_router().with_v1(v1_client());

let client = if enable_protocol_v2 {
client.with_v2(v2_client())
} else {
client
};

client
.connect_to(agent_transport)
.await?;
# Ok(())
# }
```

The client router starts the highest configured implementation. If v2
initialization negotiates v1 and a v1 implementation is configured, it switches
to the local v1 client implementation and forwards the original initialize
response. It does not send a second initialize request on the same connection.
If v2 initialization is rejected, that error is surfaced to the v2 client
implementation.
4 changes: 4 additions & 0 deletions src/agent-client-protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ pub use jsonrpc::{
};
pub use jsonrpc::{RequestCancellation, is_cancel_request_notification};

#[cfg(feature = "unstable_protocol_v2")]
pub use role::acp::AgentProtocolRouter;
#[cfg(feature = "unstable_protocol_v2")]
pub use role::acp::ClientProtocolConnector;
pub use role::{
Role, RoleId, UntypedRole,
acp::{Agent, Client, Conductor, Proxy},
Expand Down
Loading