#![allow(clippy::similar_names, clippy::expect_used)]
use std::sync::Arc;
use std::time::Duration;
use arcp::auth::BearerAuthenticator;
use arcp::envelope::Envelope;
use arcp::error::{ARCPError, ErrorCode};
use arcp::messages::{
AgentInventory, AgentInventoryEntry, AuthScheme, Capabilities, ClientIdentity, Credentials,
MessageType, SessionOpenPayload, ToolInvokePayload,
};
use arcp::runtime::context::ToolContext;
use arcp::runtime::tools::{ToolHandler, ToolRegistryBuilder};
use arcp::runtime::ARCPRuntime;
use arcp::transport::{paired, Transport};
use async_trait::async_trait;
struct EchoTool;
#[async_trait]
impl ToolHandler for EchoTool {
fn name(&self) -> &'static str {
"echo"
}
async fn invoke(
&self,
arguments: serde_json::Value,
_ctx: ToolContext,
) -> Result<serde_json::Value, ARCPError> {
Ok(arguments)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let caps = Capabilities {
agents: Some(AgentInventory::Rich(vec![AgentInventoryEntry {
name: "echo".into(),
versions: vec!["1.0.0".into(), "2.0.0".into()],
default: Some("2.0.0".into()),
}])),
..Capabilities::default()
};
let tools = ToolRegistryBuilder::new().with(Arc::new(EchoTool)).build();
let runtime = ARCPRuntime::builder()
.with_authenticator(Box::new(BearerAuthenticator::new().with_token("t", "p")))
.with_tools(tools)
.with_capabilities(caps)
.build()
.await?;
let (server_t, client_t) = paired();
let _h = runtime.serve_connection(server_t);
let mut open = Envelope::new(MessageType::SessionOpen(SessionOpenPayload {
auth: Credentials {
scheme: AuthScheme::Bearer,
token: Some("t".into()),
},
client: ClientIdentity {
kind: "agent-versions-demo".into(),
version: env!("CARGO_PKG_VERSION").into(),
fingerprint: None,
principal: None,
},
capabilities: Capabilities::default(),
}));
open.id = arcp::ids::MessageId::new();
client_t.send(open).await?;
let accepted = client_t.recv().await?.ok_or("no session.accepted")?;
let MessageType::SessionAccepted(payload) = accepted.payload else {
return Err("expected session.accepted".into());
};
let session_id = payload.session_id.clone();
println!(
"runtime advertised agents: {:?}",
payload.capabilities.agents
);
{
let mut invoke = Envelope::new(MessageType::ToolInvoke(ToolInvokePayload::new(
"echo@1.0.0",
serde_json::json!({"msg": "hello"}),
)));
invoke.session_id = Some(session_id.clone());
client_t.send(invoke).await?;
loop {
let env = tokio::time::timeout(Duration::from_secs(1), client_t.recv())
.await??
.ok_or("transport closed")?;
match env.payload {
MessageType::JobCompleted(p) => {
println!("echo@1.0.0 → JobCompleted, value={:?}", p.value);
break;
}
MessageType::JobFailed(p) => {
return Err(format!("unexpected failure: {} {}", p.code, p.message).into());
}
_ => {}
}
}
}
{
let mut invoke = Envelope::new(MessageType::ToolInvoke(ToolInvokePayload::new(
"echo@9.9.9",
serde_json::json!({"msg": "hello"}),
)));
invoke.session_id = Some(session_id);
client_t.send(invoke).await?;
loop {
let env = tokio::time::timeout(Duration::from_secs(1), client_t.recv())
.await??
.ok_or("transport closed")?;
match env.payload {
MessageType::JobFailed(p) => {
assert_eq!(p.code, ErrorCode::AgentVersionNotAvailable);
println!("echo@9.9.9 → {} ({})", p.code, p.message);
break;
}
MessageType::JobCompleted(_) => {
return Err("expected failure for missing version".into());
}
_ => {}
}
}
}
println!("done");
Ok(())
}