Skip to main content

objectiveai_sdk/cli/command/laboratories/
mod.rs

1//! `laboratories` — top-level group for laboratory containers (podman
2//! containers the conduit dials as client-side MCP servers), sibling to
3//! `agents`/`swarms`. `create` creates + starts a laboratory container;
4//! `list` streams the laboratory containers created in this state;
5//! `attach`/`detach` record/remove a laboratory id on an agent target
6//! (a tag, or an instance hierarchy). Read attachments back via
7//! `agents instances get` (the `laboratories` field). The machine's
8//! resident laboratory HOST process is daemon-managed (spawned on
9//! first need, dies with the daemon — no spawn/kill commands);
10//! `config` holds the addresses it connects to (+ the `local` toggle).
11
12use crate::cli::command::CommandRequest;
13
14pub mod attach;
15pub mod config;
16pub mod create;
17pub mod delete;
18pub mod detach;
19pub mod list;
20
21#[derive(clap::Subcommand)]
22pub enum Command {
23    /// Attach a laboratory id to an agent target.
24    Attach(attach::Command),
25    /// Configure the resident laboratory host (addresses, local).
26    Config {
27        #[command(subcommand)]
28        command: config::Command,
29    },
30    /// Create + start a laboratory container.
31    Create(create::Command),
32    /// Delete (remove) a laboratory container, reclaiming disk.
33    Delete(delete::Command),
34    /// Detach a laboratory id from an agent target.
35    Detach(detach::Command),
36    /// List the laboratory containers created in this state.
37    List(list::Command),
38}
39
40#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
41#[serde(untagged)]
42#[schemars(rename = "cli.command.laboratories.Request")]
43pub enum Request {
44    #[schemars(title = "Attach")]
45    Attach(attach::Request),
46    #[schemars(title = "AttachRequestSchema")]
47    AttachRequestSchema(attach::request_schema::Request),
48    #[schemars(title = "AttachResponseSchema")]
49    AttachResponseSchema(attach::response_schema::Request),
50    #[schemars(title = "Config")]
51    Config(config::Request),
52    #[schemars(title = "Create")]
53    Create(create::Request),
54    #[schemars(title = "CreateRequestSchema")]
55    CreateRequestSchema(create::request_schema::Request),
56    #[schemars(title = "CreateResponseSchema")]
57    CreateResponseSchema(create::response_schema::Request),
58    #[schemars(title = "Delete")]
59    Delete(delete::Request),
60    #[schemars(title = "DeleteRequestSchema")]
61    DeleteRequestSchema(delete::request_schema::Request),
62    #[schemars(title = "DeleteResponseSchema")]
63    DeleteResponseSchema(delete::response_schema::Request),
64    #[schemars(title = "Detach")]
65    Detach(detach::Request),
66    #[schemars(title = "DetachRequestSchema")]
67    DetachRequestSchema(detach::request_schema::Request),
68    #[schemars(title = "DetachResponseSchema")]
69    DetachResponseSchema(detach::response_schema::Request),
70    #[schemars(title = "List")]
71    List(list::Request),
72    #[schemars(title = "ListRequestSchema")]
73    ListRequestSchema(list::request_schema::Request),
74    #[schemars(title = "ListResponseSchema")]
75    ListResponseSchema(list::response_schema::Request),
76}
77
78// Exempt from json-schema coverage: tier aggregate (see the root
79// `ResponseItem` in command.rs - TS7056).
80#[objectiveai_sdk_macros::json_schema_ignore]
81#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
82#[schemars(rename = "cli.command.laboratories.ResponseItem")]
83#[serde(untagged)]
84pub enum ResponseItem {
85    #[schemars(title = "Attach")]
86    Attach(attach::Response),
87    #[schemars(title = "AttachRequestSchema")]
88    AttachRequestSchema(attach::request_schema::Response),
89    #[schemars(title = "AttachResponseSchema")]
90    AttachResponseSchema(attach::response_schema::Response),
91    #[schemars(title = "Config")]
92    Config(config::Response),
93    #[schemars(title = "Create")]
94    Create(create::Response),
95    #[schemars(title = "CreateRequestSchema")]
96    CreateRequestSchema(create::request_schema::Response),
97    #[schemars(title = "CreateResponseSchema")]
98    CreateResponseSchema(create::response_schema::Response),
99    #[schemars(title = "Delete")]
100    Delete(delete::Response),
101    #[schemars(title = "DeleteRequestSchema")]
102    DeleteRequestSchema(delete::request_schema::Response),
103    #[schemars(title = "DeleteResponseSchema")]
104    DeleteResponseSchema(delete::response_schema::Response),
105    #[schemars(title = "Detach")]
106    Detach(detach::Response),
107    #[schemars(title = "DetachRequestSchema")]
108    DetachRequestSchema(detach::request_schema::Response),
109    #[schemars(title = "DetachResponseSchema")]
110    DetachResponseSchema(detach::response_schema::Response),
111    #[schemars(title = "List")]
112    List(list::ResponseItem),
113    #[schemars(title = "ListRequestSchema")]
114    ListRequestSchema(list::request_schema::Response),
115    #[schemars(title = "ListResponseSchema")]
116    ListResponseSchema(list::response_schema::Response),
117}
118
119#[cfg(feature = "mcp")]
120impl crate::cli::command::CommandResponse for ResponseItem {
121    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
122        match self {
123            ResponseItem::Attach(v) => v.into_mcp(),
124            ResponseItem::AttachRequestSchema(v) => v.into_mcp(),
125            ResponseItem::AttachResponseSchema(v) => v.into_mcp(),
126            ResponseItem::Config(v) => v.into_mcp(),
127            ResponseItem::Create(v) => v.into_mcp(),
128            ResponseItem::CreateRequestSchema(v) => v.into_mcp(),
129            ResponseItem::CreateResponseSchema(v) => v.into_mcp(),
130            ResponseItem::Delete(v) => v.into_mcp(),
131            ResponseItem::DeleteRequestSchema(v) => v.into_mcp(),
132            ResponseItem::DeleteResponseSchema(v) => v.into_mcp(),
133            ResponseItem::Detach(v) => v.into_mcp(),
134            ResponseItem::DetachRequestSchema(v) => v.into_mcp(),
135            ResponseItem::DetachResponseSchema(v) => v.into_mcp(),
136            ResponseItem::List(v) => v.into_mcp(),
137            ResponseItem::ListRequestSchema(v) => v.into_mcp(),
138            ResponseItem::ListResponseSchema(v) => v.into_mcp(),
139        }
140    }
141}
142
143impl TryFrom<Command> for Request {
144    type Error = crate::cli::command::FromArgsError;
145    fn try_from(command: Command) -> Result<Self, Self::Error> {
146        match command {
147            Command::Attach(cmd) => match cmd.schema {
148                None => Ok(Request::Attach(attach::Request::try_from(cmd.args)?)),
149                Some(attach::Schema::RequestSchema(args)) => Ok(Request::AttachRequestSchema(
150                    attach::request_schema::Request::try_from(args)?,
151                )),
152                Some(attach::Schema::ResponseSchema(args)) => Ok(Request::AttachResponseSchema(
153                    attach::response_schema::Request::try_from(args)?,
154                )),
155            },
156            Command::Config { command } => {
157                Ok(Request::Config(config::Request::try_from(command)?))
158            }
159            Command::Create(cmd) => match cmd.schema {
160                None => Ok(Request::Create(create::Request::try_from(cmd.args)?)),
161                Some(create::Schema::RequestSchema(args)) => Ok(Request::CreateRequestSchema(
162                    create::request_schema::Request::try_from(args)?,
163                )),
164                Some(create::Schema::ResponseSchema(args)) => Ok(Request::CreateResponseSchema(
165                    create::response_schema::Request::try_from(args)?,
166                )),
167            },
168            Command::Delete(cmd) => match cmd.schema {
169                None => Ok(Request::Delete(delete::Request::try_from(cmd.args)?)),
170                Some(delete::Schema::RequestSchema(args)) => Ok(Request::DeleteRequestSchema(
171                    delete::request_schema::Request::try_from(args)?,
172                )),
173                Some(delete::Schema::ResponseSchema(args)) => Ok(Request::DeleteResponseSchema(
174                    delete::response_schema::Request::try_from(args)?,
175                )),
176            },
177            Command::Detach(cmd) => match cmd.schema {
178                None => Ok(Request::Detach(detach::Request::try_from(cmd.args)?)),
179                Some(detach::Schema::RequestSchema(args)) => Ok(Request::DetachRequestSchema(
180                    detach::request_schema::Request::try_from(args)?,
181                )),
182                Some(detach::Schema::ResponseSchema(args)) => Ok(Request::DetachResponseSchema(
183                    detach::response_schema::Request::try_from(args)?,
184                )),
185            },
186            Command::List(cmd) => match cmd.schema {
187                None => Ok(Request::List(list::Request::try_from(cmd.args)?)),
188                Some(list::Schema::RequestSchema(args)) => Ok(Request::ListRequestSchema(
189                    list::request_schema::Request::try_from(args)?,
190                )),
191                Some(list::Schema::ResponseSchema(args)) => Ok(Request::ListResponseSchema(
192                    list::response_schema::Request::try_from(args)?,
193                )),
194            },
195        }
196    }
197}
198
199impl CommandRequest for Request {
200    fn request_base(&self) -> &crate::cli::command::RequestBase {
201        match self {
202            Request::Attach(inner) => inner.request_base(),
203            Request::AttachRequestSchema(inner) => inner.request_base(),
204            Request::AttachResponseSchema(inner) => inner.request_base(),
205            Request::Config(inner) => inner.request_base(),
206            Request::Create(inner) => inner.request_base(),
207            Request::CreateRequestSchema(inner) => inner.request_base(),
208            Request::CreateResponseSchema(inner) => inner.request_base(),
209            Request::Delete(inner) => inner.request_base(),
210            Request::DeleteRequestSchema(inner) => inner.request_base(),
211            Request::DeleteResponseSchema(inner) => inner.request_base(),
212            Request::Detach(inner) => inner.request_base(),
213            Request::DetachRequestSchema(inner) => inner.request_base(),
214            Request::DetachResponseSchema(inner) => inner.request_base(),
215            Request::List(inner) => inner.request_base(),
216            Request::ListRequestSchema(inner) => inner.request_base(),
217            Request::ListResponseSchema(inner) => inner.request_base(),
218        }
219    }
220
221    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
222        match self {
223            Request::Attach(inner) => inner.request_base_mut(),
224            Request::AttachRequestSchema(inner) => inner.request_base_mut(),
225            Request::AttachResponseSchema(inner) => inner.request_base_mut(),
226            Request::Config(inner) => inner.request_base_mut(),
227            Request::Create(inner) => inner.request_base_mut(),
228            Request::CreateRequestSchema(inner) => inner.request_base_mut(),
229            Request::CreateResponseSchema(inner) => inner.request_base_mut(),
230            Request::Delete(inner) => inner.request_base_mut(),
231            Request::DeleteRequestSchema(inner) => inner.request_base_mut(),
232            Request::DeleteResponseSchema(inner) => inner.request_base_mut(),
233            Request::Detach(inner) => inner.request_base_mut(),
234            Request::DetachRequestSchema(inner) => inner.request_base_mut(),
235            Request::DetachResponseSchema(inner) => inner.request_base_mut(),
236            Request::List(inner) => inner.request_base_mut(),
237            Request::ListRequestSchema(inner) => inner.request_base_mut(),
238            Request::ListResponseSchema(inner) => inner.request_base_mut(),
239        }
240    }
241}
242
243#[cfg(feature = "cli-executor")]
244pub async fn execute<E: crate::cli::command::CommandExecutor>(
245    executor: &E,
246    request: Request,
247    agent_arguments: Option<&crate::cli::command::AgentArguments>,
248) -> Result<
249    std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>>,
250    E::Error,
251> {
252    use futures::StreamExt as _;
253    let stream: std::pin::Pin<
254        Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>,
255    > = match request {
256        Request::Attach(req) => {
257            let value = attach::execute(executor, req, agent_arguments).await?;
258            Box::pin(crate::cli::command::StreamOnce::new(Ok(
259                ResponseItem::Attach(value),
260            )))
261        }
262        Request::AttachRequestSchema(req) => {
263            let value = attach::request_schema::execute(executor, req, agent_arguments).await?;
264            Box::pin(crate::cli::command::StreamOnce::new(Ok(
265                ResponseItem::AttachRequestSchema(value),
266            )))
267        }
268        Request::AttachResponseSchema(req) => {
269            let value = attach::response_schema::execute(executor, req, agent_arguments).await?;
270            Box::pin(crate::cli::command::StreamOnce::new(Ok(
271                ResponseItem::AttachResponseSchema(value),
272            )))
273        }
274        Request::Config(req) => {
275            let inner = config::execute(executor, req, agent_arguments).await?;
276            Box::pin(inner.map(|r| r.map(ResponseItem::Config)))
277        }
278        Request::Create(req) => {
279            let value = create::execute(executor, req, agent_arguments).await?;
280            Box::pin(crate::cli::command::StreamOnce::new(Ok(
281                ResponseItem::Create(value),
282            )))
283        }
284        Request::CreateRequestSchema(req) => {
285            let value = create::request_schema::execute(executor, req, agent_arguments).await?;
286            Box::pin(crate::cli::command::StreamOnce::new(Ok(
287                ResponseItem::CreateRequestSchema(value),
288            )))
289        }
290        Request::CreateResponseSchema(req) => {
291            let value = create::response_schema::execute(executor, req, agent_arguments).await?;
292            Box::pin(crate::cli::command::StreamOnce::new(Ok(
293                ResponseItem::CreateResponseSchema(value),
294            )))
295        }
296        Request::Delete(req) => {
297            let value = delete::execute(executor, req, agent_arguments).await?;
298            Box::pin(crate::cli::command::StreamOnce::new(Ok(
299                ResponseItem::Delete(value),
300            )))
301        }
302        Request::DeleteRequestSchema(req) => {
303            let value = delete::request_schema::execute(executor, req, agent_arguments).await?;
304            Box::pin(crate::cli::command::StreamOnce::new(Ok(
305                ResponseItem::DeleteRequestSchema(value),
306            )))
307        }
308        Request::DeleteResponseSchema(req) => {
309            let value = delete::response_schema::execute(executor, req, agent_arguments).await?;
310            Box::pin(crate::cli::command::StreamOnce::new(Ok(
311                ResponseItem::DeleteResponseSchema(value),
312            )))
313        }
314        Request::Detach(req) => {
315            let value = detach::execute(executor, req, agent_arguments).await?;
316            Box::pin(crate::cli::command::StreamOnce::new(Ok(
317                ResponseItem::Detach(value),
318            )))
319        }
320        Request::DetachRequestSchema(req) => {
321            let value = detach::request_schema::execute(executor, req, agent_arguments).await?;
322            Box::pin(crate::cli::command::StreamOnce::new(Ok(
323                ResponseItem::DetachRequestSchema(value),
324            )))
325        }
326        Request::DetachResponseSchema(req) => {
327            let value = detach::response_schema::execute(executor, req, agent_arguments).await?;
328            Box::pin(crate::cli::command::StreamOnce::new(Ok(
329                ResponseItem::DetachResponseSchema(value),
330            )))
331        }
332        Request::List(req) => {
333            let inner = list::execute(executor, req, agent_arguments).await?;
334            Box::pin(inner.map(|r| r.map(ResponseItem::List)))
335        }
336        Request::ListRequestSchema(req) => {
337            let value = list::request_schema::execute(executor, req, agent_arguments).await?;
338            Box::pin(crate::cli::command::StreamOnce::new(Ok(
339                ResponseItem::ListRequestSchema(value),
340            )))
341        }
342        Request::ListResponseSchema(req) => {
343            let value = list::response_schema::execute(executor, req, agent_arguments).await?;
344            Box::pin(crate::cli::command::StreamOnce::new(Ok(
345                ResponseItem::ListResponseSchema(value),
346            )))
347        }
348    };
349    Ok(stream)
350}
351
352#[cfg(feature = "cli-executor")]
353pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
354    executor: &E,
355    request: Request,
356    transform: crate::cli::command::Transform,
357    agent_arguments: Option<&crate::cli::command::AgentArguments>,
358) -> Result<
359    std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>>,
360    E::Error,
361> {
362    let stream: std::pin::Pin<
363        Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>,
364    > = match request {
365        Request::Attach(req) => {
366            let value = attach::execute_transform(executor, req, transform, agent_arguments).await?;
367            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
368        }
369        Request::AttachRequestSchema(req) => {
370            let value = attach::request_schema::execute_transform(
371                executor,
372                req,
373                transform,
374                agent_arguments,
375            )
376            .await?;
377            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
378        }
379        Request::AttachResponseSchema(req) => {
380            let value = attach::response_schema::execute_transform(
381                executor,
382                req,
383                transform,
384                agent_arguments,
385            )
386            .await?;
387            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
388        }
389        Request::Config(req) => {
390            let inner = config::execute_transform(executor, req, transform, agent_arguments).await?;
391            Box::pin(inner)
392        }
393        Request::Create(req) => {
394            let value = create::execute_transform(executor, req, transform, agent_arguments).await?;
395            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
396        }
397        Request::CreateRequestSchema(req) => {
398            let value = create::request_schema::execute_transform(
399                executor,
400                req,
401                transform,
402                agent_arguments,
403            )
404            .await?;
405            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
406        }
407        Request::CreateResponseSchema(req) => {
408            let value = create::response_schema::execute_transform(
409                executor,
410                req,
411                transform,
412                agent_arguments,
413            )
414            .await?;
415            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
416        }
417        Request::Delete(req) => {
418            let value = delete::execute_transform(executor, req, transform, agent_arguments).await?;
419            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
420        }
421        Request::DeleteRequestSchema(req) => {
422            let value = delete::request_schema::execute_transform(
423                executor,
424                req,
425                transform,
426                agent_arguments,
427            )
428            .await?;
429            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
430        }
431        Request::DeleteResponseSchema(req) => {
432            let value = delete::response_schema::execute_transform(
433                executor,
434                req,
435                transform,
436                agent_arguments,
437            )
438            .await?;
439            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
440        }
441        Request::Detach(req) => {
442            let value = detach::execute_transform(executor, req, transform, agent_arguments).await?;
443            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
444        }
445        Request::DetachRequestSchema(req) => {
446            let value = detach::request_schema::execute_transform(
447                executor,
448                req,
449                transform,
450                agent_arguments,
451            )
452            .await?;
453            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
454        }
455        Request::DetachResponseSchema(req) => {
456            let value = detach::response_schema::execute_transform(
457                executor,
458                req,
459                transform,
460                agent_arguments,
461            )
462            .await?;
463            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
464        }
465        Request::List(req) => {
466            let inner = list::execute_transform(executor, req, transform, agent_arguments).await?;
467            Box::pin(inner)
468        }
469        Request::ListRequestSchema(req) => {
470            let value = list::request_schema::execute_transform(
471                executor,
472                req,
473                transform,
474                agent_arguments,
475            )
476            .await?;
477            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
478        }
479        Request::ListResponseSchema(req) => {
480            let value = list::response_schema::execute_transform(
481                executor,
482                req,
483                transform,
484                agent_arguments,
485            )
486            .await?;
487            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
488        }
489    };
490    Ok(stream)
491}
492
493/// `/listen` mirror of [`Request`]: one variant per child, wrapping
494/// its `ListenerExecution`. See [`crate::cli::broadcast_listener`].
495#[cfg(feature = "cli-listener")]
496pub enum ListenerExecution {
497    Attach(attach::ListenerExecution),
498    AttachRequestSchema(attach::request_schema::ListenerExecution),
499    AttachResponseSchema(attach::response_schema::ListenerExecution),
500    Config(config::ListenerExecution),
501    Create(create::ListenerExecution),
502    CreateRequestSchema(create::request_schema::ListenerExecution),
503    CreateResponseSchema(create::response_schema::ListenerExecution),
504    Delete(delete::ListenerExecution),
505    DeleteRequestSchema(delete::request_schema::ListenerExecution),
506    DeleteResponseSchema(delete::response_schema::ListenerExecution),
507    Detach(detach::ListenerExecution),
508    DetachRequestSchema(detach::request_schema::ListenerExecution),
509    DetachResponseSchema(detach::response_schema::ListenerExecution),
510    List(list::ListenerExecution),
511    ListRequestSchema(list::request_schema::ListenerExecution),
512    ListResponseSchema(list::response_schema::ListenerExecution),
513}