use std::{
sync::Arc,
time::{Duration, Instant},
};
use mentra::a2a::{A2aClient, A2aError as ClientError};
use super::{RemoteAgent, RemoteAgents, Roster};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Route {
Connected,
Retry,
Backoff(Duration),
Removed,
Unknown,
}
#[derive(Debug)]
pub(crate) struct Reached {
pub(crate) client: Arc<A2aClient>,
pub(crate) endpoint: String,
}
#[derive(Debug)]
pub(crate) enum Unreached {
Backoff(Duration),
Failed {
error: ClientError,
retry_in: Duration,
},
Removed,
Unknown,
}
impl RemoteAgent {
fn retry_in(&self, backoff: Duration) -> Duration {
let attempted = *self
.attempted
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
backoff.saturating_sub(attempted.elapsed())
}
fn claim_retry(&self, backoff: Duration) -> Result<(), Duration> {
let mut attempted = self
.attempted
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let wait = backoff.saturating_sub(attempted.elapsed());
if !wait.is_zero() {
return Err(wait);
}
*attempted = Instant::now();
Ok(())
}
}
impl RemoteAgents {
pub(crate) fn route(&self, name: &str) -> Route {
let roster = self.snapshot();
let Some(agent) = roster.get(name) else {
return roster.absent(name);
};
if agent.client.is_some() {
return Route::Connected;
}
match agent.retry_in(self.backoff) {
wait if wait.is_zero() => Route::Retry,
wait => Route::Backoff(wait),
}
}
pub(crate) async fn reach(&self, name: &str) -> Result<Reached, Unreached> {
let roster = self.snapshot();
let Some(agent) = roster.get(name) else {
return Err(match roster.absent(name) {
Route::Removed => Unreached::Removed,
_ => Unreached::Unknown,
});
};
let endpoint = agent.agent.endpoint();
if let Some(client) = &agent.client {
return Ok(Reached {
client: Arc::clone(client),
endpoint,
});
}
agent
.claim_retry(self.backoff)
.map_err(Unreached::Backoff)?;
let client = A2aClient::connect(agent.agent.config())
.await
.map(Arc::new)
.map_err(|error| Unreached::Failed {
error,
retry_in: self.backoff,
})?;
if self.install(agent, &client) {
self.changed();
}
Ok(Reached { client, endpoint })
}
fn install(&self, agent: &Arc<RemoteAgent>, client: &Arc<A2aClient>) -> bool {
let mut roster = self
.roster
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let Some(at) = roster.agents.iter().position(|had| Arc::ptr_eq(had, agent)) else {
return false;
};
let mut agents = roster.agents.clone();
agents[at] = Arc::new(RemoteAgent::new(
agent.agent.clone(),
Some(Arc::clone(client)),
));
*roster = Arc::new(Roster {
agents,
removed: roster.removed.clone(),
});
true
}
}
impl Roster {
fn absent(&self, name: &str) -> Route {
if self.removed.contains(name) {
Route::Removed
} else {
Route::Unknown
}
}
}
#[cfg(test)]
mod tests;