use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{anyhow, bail};
use axum::Router;
use crate::app::{App, Mode};
use crate::host::Host;
pub type MicroVm = Arc<dyn Fn(everruns::AgentBuilder) -> everruns::AgentBuilder + Send + Sync>;
#[must_use]
pub struct ServerBuilder {
app: App,
mode: Mode,
data_dir: Option<PathBuf>,
microvm: Option<MicroVm>,
}
impl ServerBuilder {
pub fn data_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.data_dir = Some(dir.into());
self
}
pub fn microvm(
mut self,
f: impl Fn(everruns::AgentBuilder) -> everruns::AgentBuilder + Send + Sync + 'static,
) -> Self {
self.microvm = Some(Arc::new(f));
self
}
pub fn build(self) -> crate::Result<Server> {
let Self {
app,
mode,
data_dir,
microvm,
} = self;
if !app.errors().is_empty() {
bail!(
"{} problem(s) found during discovery: {}",
app.errors().len(),
app.errors().join("; ")
);
}
let missing = missing_secrets(&app);
if mode == Mode::Start && !missing.is_empty() {
bail!("missing secrets: {}", missing.join(", "));
}
let host = Host::new(app.clone(), mode, data_dir)?;
if let Some(microvm) = microvm {
host.set_microvm(microvm);
}
for agent in &app.inner.agents {
host.build_agent(agent, None, false)?;
}
Ok(Server { host })
}
}
#[derive(Clone)]
pub struct Server {
pub(crate) host: Arc<Host>,
}
impl Server {
pub fn builder(app: App, mode: Mode) -> ServerBuilder {
ServerBuilder {
app,
mode,
data_dir: None,
microvm: None,
}
}
pub fn new(app: App, mode: Mode, data_dir: Option<PathBuf>) -> crate::Result<Self> {
let builder = Self::builder(app, mode);
match data_dir {
Some(dir) => builder.data_dir(dir),
None => builder,
}
.build()
}
pub fn app(&self) -> &App {
&self.host.app
}
pub fn mode(&self) -> Mode {
self.host.mode
}
pub fn build_id(&self) -> &str {
&self.host.build_id
}
pub fn router(&self) -> Router {
crate::server::router(self.host.clone())
}
pub fn busy(&self) -> bool {
self.host.busy()
}
pub fn default_agent(&self) -> Option<String> {
self.host
.app
.default_agent()
.map(|agent| agent.name.to_string())
}
pub fn spawn_schedules(&self) {
crate::scheduler::spawn(&self.host);
}
#[cfg(feature = "ag-ui")]
pub async fn ag_ui(&self, agent: &str, body: &[u8]) -> axum::response::Response {
crate::ag_ui::respond(&self.host, agent, body).await
}
}
pub(crate) fn missing_secrets(app: &App) -> Vec<String> {
app.manifest()
.secrets
.iter()
.map(|secret| secret.name.clone())
.filter(|name| std::env::var_os(name).is_none_or(|value| value.is_empty()))
.collect()
}
pub fn data_dir() -> crate::Result<PathBuf> {
if let Ok(url) = std::env::var("DATABASE_URL") {
return match url
.strip_prefix("sqlite://")
.or_else(|| url.strip_prefix("sqlite:"))
{
Some(path) => Ok(PathBuf::from(path)),
None => Err(anyhow!(
"DATABASE_URL `{url}` is not SQLite; this proof of concept stores sessions in SQLite only"
)),
};
}
Ok(std::env::var_os("SERVE_DATA_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(".serve")))
}