objectiveai_cli/command/daemon/
spawn.rs1use std::pin::Pin;
17
18use futures::{Stream, StreamExt};
19use objectiveai_sdk::cli::command::daemon::spawn::{Request, ResponseItem};
20use objectiveai_sdk::cli::command::plugins::run::{Path as RunPath, Request as RunRequest};
21
22use crate::context::Context;
23use crate::error::Error;
24
25type ItemStream = Pin<Box<dyn Stream<Item = Result<ResponseItem, Error>> + Send>>;
26
27pub async fn execute(ctx: &Context, request: Request) -> Result<ItemStream, Error> {
28 let foreground = request
29 .dangerous_advanced
30 .as_ref()
31 .and_then(|a| a.foreground)
32 .unwrap_or(false);
33 if foreground {
34 execute_foreground(ctx).await
35 } else {
36 spawn(ctx).await?;
40 Ok(Box::pin(futures::stream::once(async move {
41 Ok::<ResponseItem, Error>(ResponseItem { ok: true })
42 })))
43 }
44}
45
46pub async fn spawn(ctx: &Context) -> Result<String, Error> {
51 let lock_dir = ctx.filesystem.state_dir().join("locks");
52 let exe = std::env::current_exe().map_err(|e| Error::Spawn("current_exe".into(), e))?;
53 crate::spawn::spawn_until_lock_published(
54 &exe,
55 &lock_dir,
56 super::DAEMON_LOCK_KEY,
57 |cmd| {
58 cmd.arg("daemon")
59 .arg("spawn")
60 .arg("--dangerous-advanced")
61 .arg("{\"foreground\":true}");
62 crate::spawn::apply_config_env(cmd, &ctx.config);
63 },
64 )
65 .await
66}
67
68async fn execute_foreground(ctx: &Context) -> Result<ItemStream, Error> {
70 let lock_dir = ctx.filesystem.state_dir().join("locks");
71 let lock_err = |e: std::io::Error| Error::Lockfile {
72 key: super::DAEMON_LOCK_KEY.to_string(),
73 source: e,
74 };
75
76 let init = objectiveai_sdk::lockfile::wait_acquire(
78 &lock_dir,
79 super::DAEMON_INIT_LOCK_KEY,
80 "initializing",
81 )
82 .await
83 .map_err(lock_err)?;
84
85 let claim = match objectiveai_sdk::lockfile::try_acquire(
86 &lock_dir,
87 super::DAEMON_LOCK_KEY,
88 "ready",
89 )
90 .await
91 {
92 None => {
94 let _ = init.release();
95 return Ok(Box::pin(futures::stream::empty()));
96 }
97 Some(claim) => claim,
98 };
99 init.release().map_err(lock_err)?;
100
101 let manifests: Vec<crate::filesystem::plugins::Manifest> = ctx
106 .filesystem
107 .list_plugins(0, usize::MAX)
108 .await
109 .into_iter()
110 .filter(|m| m.daemon)
111 .collect();
112 let mut streams = Vec::new();
113 for manifest in manifests {
114 let request = RunRequest {
115 path_type: RunPath::PluginsRun,
116 owner: manifest.owner,
117 name: manifest.name,
118 version: manifest.version,
119 args: vec!["daemon".to_string(), "begin".to_string()],
120 base: Default::default(),
121 };
122 let stream = crate::command::plugins::run::execute(ctx, request).await?;
123 streams.push(stream);
124 }
125
126 let stream = async_stream::stream! {
127 let _claim = claim;
131
132 let mut drains = futures::stream::FuturesUnordered::new();
133 for plugin_stream in streams {
134 drains.push(async move {
135 let mut plugin_stream = plugin_stream;
136 while plugin_stream.next().await.is_some() {}
140 });
141 }
142
143 yield Ok::<ResponseItem, Error>(ResponseItem { ok: true });
146
147 if drains.is_empty() {
148 std::future::pending::<()>().await;
150 } else {
151 let _ = drains.next().await;
153 }
154 };
155 Ok(Box::pin(stream))
156}
157
158pub mod request_schema {
159 use objectiveai_sdk::cli::command::daemon::spawn as sdk;
160 use objectiveai_sdk::cli::command::daemon::spawn::request_schema::{Request, Response};
161
162 use crate::context::Context;
163 use crate::error::Error;
164
165 pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
166 Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Request)))
167 }
168}
169
170pub mod response_schema {
171 use objectiveai_sdk::cli::command::daemon::spawn as sdk;
172 use objectiveai_sdk::cli::command::daemon::spawn::response_schema::{Request, Response};
173
174 use crate::context::Context;
175 use crate::error::Error;
176
177 pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
178 Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::ResponseItem)))
179 }
180}