use std::{
sync::Arc,
time::{Duration, Instant},
};
use mentra::a2a::A2aClient;
use super::{ConfiguredAgents, RemoteAgent, Roster};
use crate::agents::{Resolved, Route, Unresolved};
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(())
}
fn resolved(&self, client: Arc<A2aClient>) -> Resolved {
Resolved::over_a2a(client, self.name()).with_endpoint(self.agent.endpoint())
}
}
impl ConfiguredAgents {
pub(crate) fn standing(&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::Known;
}
match agent.retry_in(self.backoff) {
wait if wait.is_zero() => Route::Known,
wait => Route::Backoff(wait),
}
}
pub(crate) async fn reach(&self, name: &str) -> Result<Resolved, Unresolved> {
let roster = self.snapshot();
let Some(agent) = roster.get(name) else {
return Err(match roster.absent(name) {
Route::Refused(reason) => Unresolved::Refused(reason),
_ => Unresolved::Unknown,
});
};
if let Some(client) = &agent.client {
return Ok(agent.resolved(Arc::clone(client)));
}
agent
.claim_retry(self.backoff)
.map_err(Unresolved::Backoff)?;
let client = A2aClient::connect(agent.agent.config())
.await
.map(Arc::new)
.map_err(|error| Unresolved::Unreachable {
error: error.to_string(),
retry_in: Some(self.backoff),
})?;
if self.install(agent, &client) {
self.changed();
}
Ok(agent.resolved(client))
}
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::Refused(format!(
"the remote agent `{name}` is no longer configured for this workspace"
))
} else {
Route::Unknown
}
}
}
#[cfg(test)]
mod tests;