use std::pin::Pin;
use futures::{Stream, StreamExt};
use objectiveai_sdk::cli::command::daemon::spawn::{Request, ResponseItem};
use objectiveai_sdk::cli::command::plugins::run::{Path as RunPath, Request as RunRequest};
use crate::context::Context;
use crate::error::Error;
type ItemStream = Pin<Box<dyn Stream<Item = Result<ResponseItem, Error>> + Send>>;
pub async fn execute(ctx: &Context, request: Request) -> Result<ItemStream, Error> {
let foreground = request
.dangerous_advanced
.as_ref()
.and_then(|a| a.foreground)
.unwrap_or(false);
if foreground {
execute_foreground(ctx).await
} else {
spawn(ctx).await?;
Ok(Box::pin(futures::stream::once(async move {
Ok::<ResponseItem, Error>(ResponseItem { ok: true })
})))
}
}
pub async fn spawn(ctx: &Context) -> Result<String, Error> {
let lock_dir = ctx.filesystem.state_dir().join("locks");
let exe = std::env::current_exe().map_err(|e| Error::Spawn("current_exe".into(), e))?;
crate::spawn::spawn_until_lock_published(
&exe,
&lock_dir,
super::DAEMON_LOCK_KEY,
|cmd| {
cmd.arg("daemon")
.arg("spawn")
.arg("--dangerous-advanced")
.arg("{\"foreground\":true}");
crate::spawn::apply_config_env(cmd, &ctx.config);
},
)
.await
}
async fn execute_foreground(ctx: &Context) -> Result<ItemStream, Error> {
let lock_dir = ctx.filesystem.state_dir().join("locks");
let lock_err = |e: std::io::Error| Error::Lockfile {
key: super::DAEMON_LOCK_KEY.to_string(),
source: e,
};
let init = objectiveai_sdk::lockfile::wait_acquire(
&lock_dir,
super::DAEMON_INIT_LOCK_KEY,
"initializing",
)
.await
.map_err(lock_err)?;
let claim = match objectiveai_sdk::lockfile::try_acquire(
&lock_dir,
super::DAEMON_LOCK_KEY,
"ready",
)
.await
{
None => {
let _ = init.release();
return Ok(Box::pin(futures::stream::empty()));
}
Some(claim) => claim,
};
init.release().map_err(lock_err)?;
let manifests: Vec<crate::filesystem::plugins::Manifest> = ctx
.filesystem
.list_plugins(0, usize::MAX)
.await
.into_iter()
.filter(|m| m.daemon)
.collect();
let mut streams = Vec::new();
for manifest in manifests {
let request = RunRequest {
path_type: RunPath::PluginsRun,
owner: manifest.owner,
name: manifest.name,
version: manifest.version,
args: vec!["daemon".to_string(), "begin".to_string()],
base: Default::default(),
};
let stream = crate::command::plugins::run::execute(ctx, request).await?;
streams.push(stream);
}
let stream = async_stream::stream! {
let _claim = claim;
let mut drains = futures::stream::FuturesUnordered::new();
for plugin_stream in streams {
drains.push(async move {
let mut plugin_stream = plugin_stream;
while plugin_stream.next().await.is_some() {}
});
}
yield Ok::<ResponseItem, Error>(ResponseItem { ok: true });
if drains.is_empty() {
std::future::pending::<()>().await;
} else {
let _ = drains.next().await;
}
};
Ok(Box::pin(stream))
}
pub mod request_schema {
use objectiveai_sdk::cli::command::daemon::spawn as sdk;
use objectiveai_sdk::cli::command::daemon::spawn::request_schema::{Request, Response};
use crate::context::Context;
use crate::error::Error;
pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Request)))
}
}
pub mod response_schema {
use objectiveai_sdk::cli::command::daemon::spawn as sdk;
use objectiveai_sdk::cli::command::daemon::spawn::response_schema::{Request, Response};
use crate::context::Context;
use crate::error::Error;
pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::ResponseItem)))
}
}