Skip to main content

objectiveai_cli/command/agents/instances/
list.rs

1//! `agents instances list` — enumerate the DIRECT children of one or
2//! more resolved targets, with per-agent aggregates (tags, queued
3//! count, spawn/active timestamps, total logged messages). Backed by
4//! `db::instances::list_under_parent`.
5
6use std::collections::HashSet;
7use std::pin::Pin;
8
9use futures::stream::FuturesUnordered;
10use futures::{Stream, StreamExt};
11use objectiveai_sdk::cli::command::agents::instances::list::{Request, ResponseItem};
12
13use crate::context::Context;
14use crate::error::Error;
15
16type ItemStream = Pin<Box<dyn Stream<Item = Result<ResponseItem, Error>> + Send>>;
17
18pub async fn execute(ctx: &Context, request: Request) -> Result<ItemStream, Error> {
19    // `all` and targets are mutually exclusive. Clap enforces this on
20    // the argv path; requests arriving through the JSON front door
21    // (`--request`, /execute) are validated here.
22    let all = request.all == Some(true);
23    if all && !request.targets.is_empty() {
24        return Err(Error::Instance(
25            "`all` is mutually exclusive with `targets`".to_string(),
26        ));
27    }
28    let db = ctx.db_client().await?.clone();
29    if all {
30        let stream = async_stream::stream! {
31            match crate::db::instances::list_all(&db).await {
32                Ok(items) => {
33                    for item in items {
34                        yield Ok(item);
35                    }
36                }
37                Err(e) => yield Err(Error::from(e)),
38            }
39        };
40        return Ok(Box::pin(stream));
41    }
42
43    let default_parent = ctx.config.agent_instance_hierarchy.clone();
44    let stream = async_stream::stream! {
45        // Resolve + query every target concurrently. GROUPED/ABSENT tags
46        // resolve to None and contribute nothing.
47        let mut inflight = FuturesUnordered::new();
48        for target in request.targets {
49            let db = db.clone();
50            let default_parent = default_parent.clone();
51            inflight.push(async move {
52                let parent = super::resolve_target(&db, target, &default_parent).await?;
53                let items = match parent {
54                    Some(p) => crate::db::instances::list_under_parent(&db, &p)
55                        .await
56                        .map_err(Error::from)?,
57                    None => Vec::new(),
58                };
59                Ok::<Vec<ResponseItem>, Error>(items)
60            });
61        }
62        // Yield each child the moment its target's query lands — never
63        // waiting for the slowest. A `seen` set dedups by AIH across
64        // overlapping/nested targets (the BTreeMap's old job, minus the
65        // global sort that collecting would have forced).
66        let mut seen = HashSet::new();
67        while let Some(result) = inflight.next().await {
68            match result {
69                Ok(items) => {
70                    for item in items {
71                        if seen.insert(item.agent_instance_hierarchy.clone()) {
72                            yield Ok(item);
73                        }
74                    }
75                }
76                Err(e) => yield Err(e),
77            }
78        }
79    };
80    Ok(Box::pin(stream))
81}
82
83pub mod request_schema {
84    use objectiveai_sdk::cli::command::agents::instances::list as sdk;
85    use objectiveai_sdk::cli::command::agents::instances::list::request_schema::{Request, Response};
86
87    use crate::context::Context;
88    use crate::error::Error;
89
90    pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
91        Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Request)))
92    }
93}
94
95pub mod response_schema {
96    use objectiveai_sdk::cli::command::agents::instances::list as sdk;
97    use objectiveai_sdk::cli::command::agents::instances::list::response_schema::{Request, Response};
98
99    use crate::context::Context;
100    use crate::error::Error;
101
102    pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
103        Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::ResponseItem)))
104    }
105}