use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use async_trait::async_trait;
use crate::auth::Principal;
use crate::dispatch::DispatchError;
use crate::tool::{ToolDescriptor, ToolResponse};
#[async_trait]
pub trait DispatchHost: Send + Sync {
async fn dispatch(
&self,
principal: &Principal,
tool_name: &str,
raw_args: serde_json::Value,
external_session_id: Option<&str>,
) -> Result<ToolResponse, DispatchError>;
fn list_tools(&self) -> Vec<ToolDescriptor>;
}
#[derive(Debug, Clone, Default)]
pub struct ShutdownToken {
inner: Arc<AtomicBool>,
}
impl ShutdownToken {
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.inner.store(true, Ordering::SeqCst);
}
pub fn is_cancelled(&self) -> bool {
self.inner.load(Ordering::SeqCst)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum FrontendError {
#[error("transport IO error: {0}")]
Io(#[source] std::io::Error),
#[error("frontend backend error: {0}")]
Backend(#[source] Box<dyn std::error::Error + Send + Sync>),
}
impl FrontendError {
pub fn backend<E>(err: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
Self::Backend(Box::new(err))
}
}
#[async_trait]
pub trait Frontend: Send {
async fn serve(
self,
host: Arc<dyn DispatchHost>,
shutdown: ShutdownToken,
) -> Result<(), FrontendError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shutdown_token_round_trip() {
let tok = ShutdownToken::new();
assert!(!tok.is_cancelled());
let clone = tok.clone();
clone.cancel();
assert!(tok.is_cancelled());
clone.cancel();
assert!(tok.is_cancelled());
}
}