Skip to main content

objectiveai_cli/
retrieve.rs

1//! Local resolution of agents / swarms / functions / profiles.
2//!
3//! These commands resolve content directly — never via the ObjectiveAI
4//! API. Allowed remotes are **GitHub** (via the SDK `http::github`
5//! methods, authenticated with the client's `x_github_authorization`)
6//! and **Client** (the CLI's own local storage — the CLI *is* the
7//! client); `mock` is rejected.
8//!
9//! - `get_*` resolves a single definition and returns it verbatim as the
10//!   base definition (no ID computation, no embedded-agent resolution).
11//! - `list` enumerates the local filesystem only and returns paths.
12
13use std::pin::Pin;
14
15use futures::Stream;
16use objectiveai_sdk::agent::RemoteAgentBaseWithFallbacks;
17use objectiveai_sdk::cli::command::agents::get::Response as GetAgentResponse;
18use objectiveai_sdk::cli::command::functions::get::Response as GetFunctionResponse;
19use objectiveai_sdk::cli::command::functions::profiles::get::Response as GetProfileResponse;
20use objectiveai_sdk::cli::command::swarms::get::Response as GetSwarmResponse;
21use objectiveai_sdk::functions::{FullRemoteFunction, RemoteProfile};
22use objectiveai_sdk::swarm::RemoteSwarmBase;
23use objectiveai_sdk::{HttpClient, RemotePath, RemotePathCommitOptional};
24
25use crate::context::Context;
26use crate::error::Error;
27use crate::filesystem::publish::Kind;
28
29// ── list (filesystem only) ─────────────────────────────────────────────
30
31/// Streams locally-published repositories of `kind`. GitHub has no list.
32pub async fn list(
33    ctx: &Context,
34    kind: Kind,
35) -> Pin<Box<dyn Stream<Item = RemotePath> + Send>> {
36    ctx.filesystem.list(kind).await
37}
38
39// ── get ────────────────────────────────────────────────────────────────
40
41pub async fn get_agent(
42    ctx: &Context,
43    path: RemotePathCommitOptional,
44) -> Result<GetAgentResponse, Error> {
45    let http = ctx.github_http_client().await?;
46    let (inner, path) = fetch_agent_base(ctx, &http, &path).await?;
47    Ok(GetAgentResponse { path, inner })
48}
49
50pub async fn get_swarm(
51    ctx: &Context,
52    path: RemotePathCommitOptional,
53) -> Result<GetSwarmResponse, Error> {
54    let http = ctx.github_http_client().await?;
55    let (inner, path) = fetch_swarm_base(ctx, &http, &path).await?;
56    Ok(GetSwarmResponse { path, inner })
57}
58
59pub async fn get_function(
60    ctx: &Context,
61    path: RemotePathCommitOptional,
62) -> Result<GetFunctionResponse, Error> {
63    let http = ctx.github_http_client().await?;
64    let (inner, path) = fetch_function(ctx, &http, &path).await?;
65    Ok(GetFunctionResponse { path, inner })
66}
67
68pub async fn get_profile(
69    ctx: &Context,
70    path: RemotePathCommitOptional,
71) -> Result<GetProfileResponse, Error> {
72    let http = ctx.github_http_client().await?;
73    let (inner, path) = fetch_profile(ctx, &http, &path).await?;
74    Ok(GetProfileResponse { path, inner })
75}
76
77// ── base fetchers (github / filesystem; mock rejected) ──────────────────
78
79async fn fetch_agent_base(
80    ctx: &Context,
81    http: &HttpClient,
82    path: &RemotePathCommitOptional,
83) -> Result<(RemoteAgentBaseWithFallbacks, RemotePath), Error> {
84    match path {
85        RemotePathCommitOptional::Github { owner, repository, commit } => {
86            let commit =
87                resolve_github_commit(http, owner, repository, commit.as_deref())
88                    .await?;
89            let base = http
90                .github_get_agent(owner, repository, Some(&commit))
91                .await?
92                .ok_or_else(|| not_found("agent", owner, repository))?;
93            Ok((base, github_path(owner, repository, commit)))
94        }
95        RemotePathCommitOptional::Client { owner, repository, commit } => {
96            let (base, commit) = ctx
97                .filesystem
98                .read_json::<RemoteAgentBaseWithFallbacks>(
99                    Kind::Agents,
100                    owner,
101                    repository,
102                    commit.as_deref(),
103                )
104                .await?
105                .ok_or_else(|| not_found("agent", owner, repository))?;
106            Ok((base, client_path(owner, repository, commit)))
107        }
108        RemotePathCommitOptional::Mock { .. } => {
109            Err(Error::RemoteNotSupported("mock"))
110        }
111    }
112}
113
114async fn fetch_swarm_base(
115    ctx: &Context,
116    http: &HttpClient,
117    path: &RemotePathCommitOptional,
118) -> Result<(RemoteSwarmBase, RemotePath), Error> {
119    match path {
120        RemotePathCommitOptional::Github { owner, repository, commit } => {
121            let commit =
122                resolve_github_commit(http, owner, repository, commit.as_deref())
123                    .await?;
124            let base = http
125                .github_get_swarm(owner, repository, Some(&commit))
126                .await?
127                .ok_or_else(|| not_found("swarm", owner, repository))?;
128            Ok((base, github_path(owner, repository, commit)))
129        }
130        RemotePathCommitOptional::Client { owner, repository, commit } => {
131            let (base, commit) = ctx
132                .filesystem
133                .read_json::<RemoteSwarmBase>(
134                    Kind::Swarms,
135                    owner,
136                    repository,
137                    commit.as_deref(),
138                )
139                .await?
140                .ok_or_else(|| not_found("swarm", owner, repository))?;
141            Ok((base, client_path(owner, repository, commit)))
142        }
143        RemotePathCommitOptional::Mock { .. } => {
144            Err(Error::RemoteNotSupported("mock"))
145        }
146    }
147}
148
149async fn fetch_function(
150    ctx: &Context,
151    http: &HttpClient,
152    path: &RemotePathCommitOptional,
153) -> Result<(FullRemoteFunction, RemotePath), Error> {
154    match path {
155        RemotePathCommitOptional::Github { owner, repository, commit } => {
156            let commit =
157                resolve_github_commit(http, owner, repository, commit.as_deref())
158                    .await?;
159            let inner = http
160                .github_get_function(owner, repository, Some(&commit))
161                .await?
162                .ok_or_else(|| not_found("function", owner, repository))?;
163            Ok((inner, github_path(owner, repository, commit)))
164        }
165        RemotePathCommitOptional::Client { owner, repository, commit } => {
166            let (inner, commit) = ctx
167                .filesystem
168                .read_json::<FullRemoteFunction>(
169                    Kind::Functions,
170                    owner,
171                    repository,
172                    commit.as_deref(),
173                )
174                .await?
175                .ok_or_else(|| not_found("function", owner, repository))?;
176            Ok((inner, client_path(owner, repository, commit)))
177        }
178        RemotePathCommitOptional::Mock { .. } => {
179            Err(Error::RemoteNotSupported("mock"))
180        }
181    }
182}
183
184async fn fetch_profile(
185    ctx: &Context,
186    http: &HttpClient,
187    path: &RemotePathCommitOptional,
188) -> Result<(RemoteProfile, RemotePath), Error> {
189    match path {
190        RemotePathCommitOptional::Github { owner, repository, commit } => {
191            let commit =
192                resolve_github_commit(http, owner, repository, commit.as_deref())
193                    .await?;
194            let inner = http
195                .github_get_profile(owner, repository, Some(&commit))
196                .await?
197                .ok_or_else(|| not_found("profile", owner, repository))?;
198            Ok((inner, github_path(owner, repository, commit)))
199        }
200        RemotePathCommitOptional::Client { owner, repository, commit } => {
201            let (inner, commit) = ctx
202                .filesystem
203                .read_json::<RemoteProfile>(
204                    Kind::Profiles,
205                    owner,
206                    repository,
207                    commit.as_deref(),
208                )
209                .await?
210                .ok_or_else(|| not_found("profile", owner, repository))?;
211            Ok((inner, client_path(owner, repository, commit)))
212        }
213        RemotePathCommitOptional::Mock { .. } => {
214            Err(Error::RemoteNotSupported("mock"))
215        }
216    }
217}
218
219// ── helpers ─────────────────────────────────────────────────────────────
220
221async fn resolve_github_commit(
222    http: &HttpClient,
223    owner: &str,
224    repository: &str,
225    commit: Option<&str>,
226) -> Result<String, Error> {
227    match commit {
228        Some(c) => Ok(c.to_string()),
229        None => http
230            .github_resolve_commit(owner, repository)
231            .await?
232            .ok_or_else(|| Error::NotFound(format!("{owner}/{repository}"))),
233    }
234}
235
236fn github_path(owner: &str, repository: &str, commit: String) -> RemotePath {
237    RemotePath::Github {
238        owner: owner.to_string(),
239        repository: repository.to_string(),
240        commit,
241    }
242}
243
244fn client_path(owner: &str, repository: &str, commit: String) -> RemotePath {
245    RemotePath::Client {
246        owner: owner.to_string(),
247        repository: repository.to_string(),
248        commit,
249    }
250}
251
252fn not_found(kind: &str, owner: &str, repository: &str) -> Error {
253    Error::NotFound(format!("{kind} {owner}/{repository}"))
254}