use std::collections::{HashMap, VecDeque};
use bitrouter_sdk::acp::client::{AcpClient, PendingPermission, RouteError};
use bitrouter_tui::machine::{Action, Effect, Routes};
use bitrouter_tui::permission::{Decision, Policy, Prompt};
pub(crate) struct Wire<'a> {
client: &'a AcpClient,
session_id: &'a str,
outstanding: HashMap<String, PendingPermission>,
replies: VecDeque<Action>,
}
impl<'a> Wire<'a> {
pub(crate) fn new(client: &'a AcpClient, session_id: &'a str) -> Self {
Self {
client,
session_id,
outstanding: HashMap::new(),
replies: VecDeque::new(),
}
}
pub(crate) fn admit(&mut self, request: PendingPermission) -> Prompt {
let prompt = Prompt::new(
request.request_id.clone(),
request.tool_call.fields.title.clone(),
request.tool_call.tool_call_id.0.to_string(),
request.tool_call.fields.kind,
request.options.clone(),
);
self.outstanding.insert(request.request_id.clone(), request);
prompt
}
pub(crate) async fn answer(
&mut self,
request: PendingPermission,
policy: &Policy,
) -> (Decision, Prompt) {
let prompt = self.admit(request);
let (decision, effect) = bitrouter_tui::machine::decide(policy, &prompt);
let _ = self.apply(effect).await;
(decision, prompt)
}
pub(crate) fn reply(&mut self) -> Option<Action> {
self.replies.pop_front()
}
pub(crate) async fn apply(&mut self, effect: Effect) -> Option<Effect> {
match effect {
Effect::Resolve { id, outcome } => {
if let Some(request) = self.outstanding.remove(&id) {
request.resolve(outcome);
}
}
Effect::Cancel => {
let told = self.client.cancel(self.session_id).await;
self.outstanding.clear();
self.client.deny_session_permissions(self.session_id);
if let Err(error) = told {
tracing::warn!(%error, "cancelling the turn");
}
}
Effect::ListRoutes => {
self.replies.push_back(Action::Routes(
match self.client.route_list(self.session_id).await {
Ok(listed) => Ok(Routes {
available: listed.available,
current: listed.current,
}),
Err(error) => Err(format!("{error}")),
},
));
}
Effect::SetRoute(route) => self.replies.push_back(Action::Routed(
match self.client.route_set(self.session_id, &route).await {
Ok(in_force) => Ok(Some(in_force)),
Err(RouteError::InvalidRoute(message)) => {
Err(format!("route unchanged: {message}"))
}
Err(RouteError::Unavailable(message)) => Err(format!(
"route unchanged: route control is unavailable ({message})"
)),
Err(RouteError::Other(error)) => Err(format!("route unchanged: {error:#}")),
},
)),
Effect::ResetRoute => self.replies.push_back(Action::Routed(
match self.client.route_reset(self.session_id).await {
Ok(()) => Ok(None),
Err(RouteError::InvalidRoute(message)) => {
Err(format!("route unchanged: {message}"))
}
Err(RouteError::Unavailable(message)) => Err(format!(
"route unchanged: route control is unavailable ({message})"
)),
Err(RouteError::Other(error)) => Err(format!("route unchanged: {error:#}")),
},
)),
other => return Some(other),
}
None
}
}