Skip to main content

objectiveai_cli/command/functions/
list.rs

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