Skip to main content

objectiveai_cli/command/agents/
list.rs

1//! `agents list available <source>` — enumerate remote agents from a
2//! given source. Streams one `ResponseItem` per agent. Sources:
3//!
4//! - `Filesystem` / `Objectiveai` / `Mock` — delegates to the SDK's
5//!   `list_agents` HTTP endpoint with the matching `ListAgentsSource`.
6//! - `All` — filesystem + objectiveai, **de-duplicated**: ObjectiveAI
7//!   items skip anything already covered by a filesystem item. The
8//!   fetches run concurrently.
9
10use std::pin::Pin;
11
12use futures::Stream;
13use objectiveai_sdk::agent::list_agents;
14use objectiveai_sdk::agent::request::{ListAgentsRequest, ListAgentsSource};
15use objectiveai_sdk::cli::command::agents::list::{
16    Request, RequestSource, ResponseItem,
17};
18use objectiveai_sdk::RemotePath;
19
20use crate::context::Context;
21use crate::error::Error;
22
23type ItemStream = Pin<Box<dyn Stream<Item = Result<ResponseItem, Error>> + Send>>;
24
25pub async fn execute(ctx: &Context, request: Request) -> Result<ItemStream, Error> {
26    let stream: ItemStream = match request.source {
27        RequestSource::Filesystem => paths_to_stream(
28            fetch_paths(ctx, ListAgentsSource::Filesystem).await?,
29        ),
30        RequestSource::Objectiveai => paths_to_stream(
31            fetch_paths(ctx, ListAgentsSource::Objectiveai).await?,
32        ),
33        RequestSource::Mock => {
34            paths_to_stream(fetch_paths(ctx, ListAgentsSource::Mock).await?)
35        }
36        RequestSource::All => {
37            let (fs_items, oai_items) = tokio::try_join!(
38                fetch_paths(ctx, ListAgentsSource::Filesystem),
39                fetch_paths(ctx, ListAgentsSource::Objectiveai),
40            )?;
41            let items = merge_all(fs_items, oai_items);
42            Box::pin(futures::stream::iter(items.into_iter().map(Ok)))
43        }
44    };
45    Ok(stream)
46}
47
48fn paths_to_stream(paths: Vec<RemotePath>) -> ItemStream {
49    Box::pin(futures::stream::iter(
50        paths.into_iter().map(Ok),
51    ))
52}
53
54async fn fetch_paths(
55    ctx: &Context,
56    source: ListAgentsSource,
57) -> Result<Vec<RemotePath>, Error> {
58    let resp = list_agents(
59        ctx.api_client().await?,
60        ListAgentsRequest {
61            source: Some(source),
62        },
63    )
64    .await?;
65    Ok(resp.data)
66}
67
68fn merge_all(
69    fs_items: Vec<RemotePath>,
70    oai_items: Vec<RemotePath>,
71) -> Vec<ResponseItem> {
72    // `ResponseItem` is an alias of `RemotePath`, so items are paths.
73    let mut items: Vec<ResponseItem> = fs_items;
74
75    for path in oai_items {
76        let dominated = items.iter().any(|existing| existing == &path);
77        if !dominated {
78            items.push(path);
79        }
80    }
81
82    items
83}
84
85pub mod request_schema {
86    use objectiveai_sdk::cli::command::agents::list as sdk;
87    use objectiveai_sdk::cli::command::agents::list::request_schema::{Request, Response};
88
89    use crate::context::Context;
90    use crate::error::Error;
91
92    pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
93        Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Request)))
94    }
95}
96
97pub mod response_schema {
98    use objectiveai_sdk::cli::command::agents::list as sdk;
99    use objectiveai_sdk::cli::command::agents::list::response_schema::{Request, Response};
100
101    use crate::context::Context;
102    use crate::error::Error;
103
104    pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
105        Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::ResponseItem)))
106    }
107}