Skip to main content

objectiveai_sdk/cli/command/functions/execute/
mod.rs

1pub mod standard;
2pub mod swiss_system;
3
4/// CLI-surface form for the `--function*` argument family: either a
5/// fully resolved inline-or-remote spec (the JSON object form that
6/// lands on `--function-inline`, or the docker-style remote-path
7/// string on `--function`), a bare favorite name (also on `--function`
8/// — disambiguated from a remote path by the FromStr at parse time),
9/// the path to a JSON file (`--function-file`), or a Python harness
10/// — inline (`--function-python-inline`) or file
11/// (`--function-python-file`) — that produces the inline-or-remote
12/// JSON at handler time.
13///
14/// Untagged so existing on-disk JSON for `Resolved`/`Favorite`
15/// round-trips byte-identically; `File`/`PythonInline`/`PythonFile`
16/// are new variants that only appear on the cli wire side.
17#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
18#[serde(untagged)]
19#[schemars(rename = "cli.command.functions.execute.FunctionSpec")]
20pub enum FunctionSpec {
21    #[schemars(title = "Resolved")]
22    Resolved(crate::functions::FullInlineFunctionOrRemoteCommitOptional),
23    #[schemars(title = "Favorite")]
24    Favorite(String),
25    #[schemars(title = "File")]
26    File(std::path::PathBuf),
27    #[schemars(title = "PythonInline")]
28    PythonInline(String),
29    #[schemars(title = "PythonFile")]
30    PythonFile(std::path::PathBuf),
31}
32
33/// CLI-surface form for the `--profile*` argument family — same
34/// shape as [`FunctionSpec`] but wrapping the profile inline-or-remote
35/// enum. See [`FunctionSpec`] for the variant semantics.
36#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
37#[serde(untagged)]
38#[schemars(rename = "cli.command.functions.execute.ProfileSpec")]
39pub enum ProfileSpec {
40    #[schemars(title = "Resolved")]
41    Resolved(crate::functions::InlineProfileOrRemoteCommitOptional),
42    #[schemars(title = "Favorite")]
43    Favorite(String),
44    #[schemars(title = "File")]
45    File(std::path::PathBuf),
46    #[schemars(title = "PythonInline")]
47    PythonInline(String),
48    #[schemars(title = "PythonFile")]
49    PythonFile(std::path::PathBuf),
50}
51
52impl FunctionSpec {
53    /// Emit this spec back into the argv pair the cli's clap parser
54    /// will reconstruct from. Used by [`crate::cli::command::CommandRequest::into_command`].
55    pub fn push_flags(&self, out: &mut Vec<String>) {
56        use crate::cli::command::path_ref::remote_path_to_arg_string;
57        use crate::functions::FullInlineFunctionOrRemoteCommitOptional;
58        match self {
59            FunctionSpec::Resolved(FullInlineFunctionOrRemoteCommitOptional::Remote(p)) => {
60                out.push("--function".to_string());
61                out.push(remote_path_to_arg_string(p));
62            }
63            FunctionSpec::Resolved(inline @ FullInlineFunctionOrRemoteCommitOptional::Inline(_)) => {
64                out.push("--function-inline".to_string());
65                out.push(serde_json::to_string(inline).expect("function serializes"));
66            }
67            FunctionSpec::Favorite(name) => {
68                out.push("--function".to_string());
69                out.push(format!("favorite={name}"));
70            }
71            FunctionSpec::File(p) => {
72                out.push("--function-file".to_string());
73                out.push(p.to_string_lossy().into_owned());
74            }
75            FunctionSpec::PythonInline(code) => {
76                out.push("--function-python-inline".to_string());
77                out.push(code.clone());
78            }
79            FunctionSpec::PythonFile(p) => {
80                out.push("--function-python-file".to_string());
81                out.push(p.to_string_lossy().into_owned());
82            }
83        }
84    }
85}
86
87impl ProfileSpec {
88    /// Emit this spec back into the argv pair the cli's clap parser
89    /// will reconstruct from. Used by [`crate::cli::command::CommandRequest::into_command`].
90    pub fn push_flags(&self, out: &mut Vec<String>) {
91        use crate::cli::command::path_ref::remote_path_to_arg_string;
92        use crate::functions::InlineProfileOrRemoteCommitOptional;
93        match self {
94            ProfileSpec::Resolved(InlineProfileOrRemoteCommitOptional::Remote(p)) => {
95                out.push("--profile".to_string());
96                out.push(remote_path_to_arg_string(p));
97            }
98            ProfileSpec::Resolved(inline @ InlineProfileOrRemoteCommitOptional::Inline(_)) => {
99                out.push("--profile-inline".to_string());
100                out.push(serde_json::to_string(inline).expect("profile serializes"));
101            }
102            ProfileSpec::Favorite(name) => {
103                out.push("--profile".to_string());
104                out.push(format!("favorite={name}"));
105            }
106            ProfileSpec::File(p) => {
107                out.push("--profile-file".to_string());
108                out.push(p.to_string_lossy().into_owned());
109            }
110            ProfileSpec::PythonInline(code) => {
111                out.push("--profile-python-inline".to_string());
112                out.push(code.clone());
113            }
114            ProfileSpec::PythonFile(p) => {
115                out.push("--profile-python-file".to_string());
116                out.push(p.to_string_lossy().into_owned());
117            }
118        }
119    }
120}
121
122/// Exactly-one-of `--function | --function-inline | --function-file |
123/// --function-python-inline | --function-python-file`. Lives on its
124/// own sub-struct + group so the `required = true, multiple = false`
125/// enforcement is scoped to these five fields (clap derive's
126/// default-group rule would otherwise pull every outer field into
127/// the same group). The group id is `function_group` rather than
128/// `function` to avoid colliding with the bare `--function` flag's
129/// own arg name.
130#[derive(clap::Args)]
131#[group(id = "function_group", required = true, multiple = false)]
132pub struct FunctionArgs {
133    /// Remote-path or favorite ref in docker-style key=value form
134    /// (e.g. `remote=mock,name=foo` or `favorite=my-saved-fn`).
135    #[arg(long, group = "function_group")]
136    pub function: Option<String>,
137    /// Inline JSON function definition (inline or remote-object shape).
138    #[arg(long, group = "function_group")]
139    pub function_inline: Option<String>,
140    /// Path to a JSON file containing the function definition.
141    #[arg(long, group = "function_group")]
142    pub function_file: Option<std::path::PathBuf>,
143    /// Inline Python that returns the function definition.
144    #[arg(long, group = "function_group")]
145    pub function_python_inline: Option<String>,
146    /// Path to a Python file that returns the function definition.
147    #[arg(long, group = "function_group")]
148    pub function_python_file: Option<std::path::PathBuf>,
149}
150
151/// Mirror of [`FunctionArgs`] for `--profile*`. See that struct for
152/// the variant semantics + group-id rationale.
153#[derive(clap::Args)]
154#[group(id = "profile_group", required = true, multiple = false)]
155pub struct ProfileArgs {
156    /// Remote-path or favorite ref in docker-style key=value form
157    /// (e.g. `remote=mock,name=foo` or `favorite=my-saved-profile`).
158    #[arg(long, group = "profile_group")]
159    pub profile: Option<String>,
160    /// Inline JSON profile definition (inline or remote-object shape).
161    #[arg(long, group = "profile_group")]
162    pub profile_inline: Option<String>,
163    /// Path to a JSON file containing the profile definition.
164    #[arg(long, group = "profile_group")]
165    pub profile_file: Option<std::path::PathBuf>,
166    /// Inline Python that returns the profile definition.
167    #[arg(long, group = "profile_group")]
168    pub profile_python_inline: Option<String>,
169    /// Path to a Python file that returns the profile definition.
170    #[arg(long, group = "profile_group")]
171    pub profile_python_file: Option<std::path::PathBuf>,
172}
173
174impl TryFrom<FunctionArgs> for FunctionSpec {
175    type Error = crate::cli::command::FromArgsError;
176    fn try_from(args: FunctionArgs) -> Result<Self, Self::Error> {
177        use crate::cli::command::path_ref::RemotePathCommitOptionalOrFavorite;
178        use crate::functions::FullInlineFunctionOrRemoteCommitOptional;
179        if let Some(s) = args.function {
180            let parsed: RemotePathCommitOptionalOrFavorite = s
181                .parse()
182                .map_err(|e| crate::cli::command::FromArgsError::path_parse("function", e))?;
183            Ok(match parsed {
184                RemotePathCommitOptionalOrFavorite::Resolved(p) => FunctionSpec::Resolved(
185                    FullInlineFunctionOrRemoteCommitOptional::Remote(p),
186                ),
187                RemotePathCommitOptionalOrFavorite::Favorite(name) => FunctionSpec::Favorite(name),
188            })
189        } else if let Some(s) = args.function_inline {
190            let mut de = serde_json::Deserializer::from_str(&s);
191            let v = serde_path_to_error::deserialize(&mut de)
192                .map_err(|e| crate::cli::command::FromArgsError::json("function_inline", e))?;
193            Ok(FunctionSpec::Resolved(v))
194        } else if let Some(p) = args.function_file {
195            Ok(FunctionSpec::File(p))
196        } else if let Some(s) = args.function_python_inline {
197            Ok(FunctionSpec::PythonInline(s))
198        } else {
199            Ok(FunctionSpec::PythonFile(args.function_python_file.unwrap()))
200        }
201    }
202}
203
204impl TryFrom<ProfileArgs> for ProfileSpec {
205    type Error = crate::cli::command::FromArgsError;
206    fn try_from(args: ProfileArgs) -> Result<Self, Self::Error> {
207        use crate::cli::command::path_ref::RemotePathCommitOptionalOrFavorite;
208        use crate::functions::InlineProfileOrRemoteCommitOptional;
209        if let Some(s) = args.profile {
210            let parsed: RemotePathCommitOptionalOrFavorite = s
211                .parse()
212                .map_err(|e| crate::cli::command::FromArgsError::path_parse("profile", e))?;
213            Ok(match parsed {
214                RemotePathCommitOptionalOrFavorite::Resolved(p) => ProfileSpec::Resolved(
215                    InlineProfileOrRemoteCommitOptional::Remote(p),
216                ),
217                RemotePathCommitOptionalOrFavorite::Favorite(name) => ProfileSpec::Favorite(name),
218            })
219        } else if let Some(s) = args.profile_inline {
220            let mut de = serde_json::Deserializer::from_str(&s);
221            let v = serde_path_to_error::deserialize(&mut de)
222                .map_err(|e| crate::cli::command::FromArgsError::json("profile_inline", e))?;
223            Ok(ProfileSpec::Resolved(v))
224        } else if let Some(p) = args.profile_file {
225            Ok(ProfileSpec::File(p))
226        } else if let Some(s) = args.profile_python_inline {
227            Ok(ProfileSpec::PythonInline(s))
228        } else {
229            Ok(ProfileSpec::PythonFile(args.profile_python_file.unwrap()))
230        }
231    }
232}
233
234#[derive(clap::Subcommand)]
235pub enum Command {
236    Standard(standard::Command),
237    SwissSystem(swiss_system::Command),
238}
239
240#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
241#[serde(untagged)]
242#[schemars(rename = "cli.command.functions.execute.Request")]
243pub enum Request {
244    #[schemars(title = "Standard")]
245    Standard(standard::Request),
246    #[schemars(title = "StandardRequestSchema")]
247    StandardRequestSchema(standard::request_schema::Request),
248    #[schemars(title = "StandardResponseSchema")]
249    StandardResponseSchema(standard::response_schema::Request),
250    #[schemars(title = "SwissSystem")]
251    SwissSystem(swiss_system::Request),
252    #[schemars(title = "SwissSystemRequestSchema")]
253    SwissSystemRequestSchema(swiss_system::request_schema::Request),
254    #[schemars(title = "SwissSystemResponseSchema")]
255    SwissSystemResponseSchema(swiss_system::response_schema::Request),
256}
257
258// Exempt from json-schema coverage: tier aggregate (see the root
259// `ResponseItem` in command.rs - TS7056).
260#[objectiveai_sdk_macros::json_schema_ignore]
261#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
262#[schemars(rename = "cli.command.functions.execute.ResponseItem")]
263#[serde(untagged)]
264pub enum ResponseItem {
265    #[schemars(title = "Standard")]
266    Standard(standard::ResponseItem),
267    #[schemars(title = "StandardRequestSchema")]
268    StandardRequestSchema(standard::request_schema::Response),
269    #[schemars(title = "StandardResponseSchema")]
270    StandardResponseSchema(standard::response_schema::Response),
271    #[schemars(title = "SwissSystem")]
272    SwissSystem(swiss_system::ResponseItem),
273    #[schemars(title = "SwissSystemRequestSchema")]
274    SwissSystemRequestSchema(swiss_system::request_schema::Response),
275    #[schemars(title = "SwissSystemResponseSchema")]
276    SwissSystemResponseSchema(swiss_system::response_schema::Response),
277}
278
279#[cfg(feature = "mcp")]
280impl crate::cli::command::CommandResponse for ResponseItem {
281    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
282        match self {
283            ResponseItem::Standard(v) => v.into_mcp(),
284            ResponseItem::StandardRequestSchema(v) => v.into_mcp(),
285            ResponseItem::StandardResponseSchema(v) => v.into_mcp(),
286            ResponseItem::SwissSystem(v) => v.into_mcp(),
287            ResponseItem::SwissSystemRequestSchema(v) => v.into_mcp(),
288            ResponseItem::SwissSystemResponseSchema(v) => v.into_mcp(),
289        }
290    }
291}
292
293impl TryFrom<Command> for Request {
294    type Error = crate::cli::command::FromArgsError;
295    fn try_from(command: Command) -> Result<Self, Self::Error> {
296        match command {
297            Command::Standard(cmd) => match cmd.schema {
298                None => Ok(Request::Standard(standard::Request::try_from(cmd.args)?)),
299                Some(standard::Schema::RequestSchema(args)) =>
300                    Ok(Request::StandardRequestSchema(standard::request_schema::Request::try_from(args)?)),
301                Some(standard::Schema::ResponseSchema(args)) =>
302                    Ok(Request::StandardResponseSchema(standard::response_schema::Request::try_from(args)?)),
303            },
304            Command::SwissSystem(cmd) => match cmd.schema {
305                None => Ok(Request::SwissSystem(swiss_system::Request::try_from(cmd.args)?)),
306                Some(swiss_system::Schema::RequestSchema(args)) =>
307                    Ok(Request::SwissSystemRequestSchema(swiss_system::request_schema::Request::try_from(args)?)),
308                Some(swiss_system::Schema::ResponseSchema(args)) =>
309                    Ok(Request::SwissSystemResponseSchema(swiss_system::response_schema::Request::try_from(args)?)),
310            },
311        }
312    }
313}
314
315impl crate::cli::command::CommandRequest for Request {
316    fn into_command(&self) -> Vec<String> {
317        match self {
318            Request::Standard(inner) => inner.into_command(),
319            Request::StandardRequestSchema(inner) => inner.into_command(),
320            Request::StandardResponseSchema(inner) => inner.into_command(),
321            Request::SwissSystem(inner) => inner.into_command(),
322            Request::SwissSystemRequestSchema(inner) => inner.into_command(),
323            Request::SwissSystemResponseSchema(inner) => inner.into_command(),
324        }
325    }
326}
327
328#[cfg(feature = "cli-executor")]
329pub async fn execute<E: crate::cli::command::CommandExecutor>(
330    executor: &E,
331    request: Request,
332
333        agent_arguments: Option<&crate::cli::command::AgentArguments>,
334    ) -> Result<
335    std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>>,
336    E::Error,
337> {
338    use futures::StreamExt;
339    let stream: std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>> =
340        match request {
341            Request::Standard(req) => {
342                let want_streaming = req
343                    .dangerous_advanced
344                    .as_ref()
345                    .and_then(|a| a.stream)
346                    .unwrap_or(false);
347                if want_streaming {
348                    let inner = standard::execute_streaming(executor, req, agent_arguments).await?;
349                    Box::pin(inner.map(|r| r.map(ResponseItem::Standard)))
350                } else {
351                    let value = standard::execute(executor, req, agent_arguments).await?;
352                    Box::pin(crate::cli::command::StreamOnce::new(Ok(
353                        ResponseItem::Standard(standard::ResponseItem::Id(value)),
354                    )))
355                }
356            }
357            Request::StandardRequestSchema(req) => {
358                let value = standard::request_schema::execute(executor, req, agent_arguments).await?;
359                Box::pin(crate::cli::command::StreamOnce::new(Ok(
360                    ResponseItem::StandardRequestSchema(value),
361                )))
362            }
363            Request::StandardResponseSchema(req) => {
364                let value = standard::response_schema::execute(executor, req, agent_arguments).await?;
365                Box::pin(crate::cli::command::StreamOnce::new(Ok(
366                    ResponseItem::StandardResponseSchema(value),
367                )))
368            }
369            Request::SwissSystem(req) => {
370                let want_streaming = req
371                    .dangerous_advanced
372                    .as_ref()
373                    .and_then(|a| a.stream)
374                    .unwrap_or(false);
375                if want_streaming {
376                    let inner = swiss_system::execute_streaming(executor, req, agent_arguments).await?;
377                    Box::pin(inner.map(|r| r.map(ResponseItem::SwissSystem)))
378                } else {
379                    let value = swiss_system::execute(executor, req, agent_arguments).await?;
380                    Box::pin(crate::cli::command::StreamOnce::new(Ok(
381                        ResponseItem::SwissSystem(swiss_system::ResponseItem::Id(value)),
382                    )))
383                }
384            }
385            Request::SwissSystemRequestSchema(req) => {
386                let value = swiss_system::request_schema::execute(executor, req, agent_arguments).await?;
387                Box::pin(crate::cli::command::StreamOnce::new(Ok(
388                    ResponseItem::SwissSystemRequestSchema(value),
389                )))
390            }
391            Request::SwissSystemResponseSchema(req) => {
392                let value = swiss_system::response_schema::execute(executor, req, agent_arguments).await?;
393                Box::pin(crate::cli::command::StreamOnce::new(Ok(
394                    ResponseItem::SwissSystemResponseSchema(value),
395                )))
396            }
397        };
398    Ok(stream)
399}
400
401#[cfg(feature = "cli-executor")]
402pub async fn execute_jq<E: crate::cli::command::CommandExecutor>(
403    executor: &E,
404    request: Request,
405    jq: String,
406
407        agent_arguments: Option<&crate::cli::command::AgentArguments>,
408    ) -> Result<
409    std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>>,
410    E::Error,
411> {
412    let stream: std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>> =
413        match request {
414            Request::Standard(req) => {
415                let want_streaming = req
416                    .dangerous_advanced
417                    .as_ref()
418                    .and_then(|a| a.stream)
419                    .unwrap_or(false);
420                if want_streaming {
421                    let inner = standard::execute_streaming_jq(executor, req, jq, agent_arguments).await?;
422                    Box::pin(inner)
423                } else {
424                    let value = standard::execute_jq(executor, req, jq, agent_arguments).await?;
425                    Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
426                }
427            }
428            Request::StandardRequestSchema(req) => {
429                let value = standard::request_schema::execute_jq(executor, req, jq, agent_arguments).await?;
430                Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
431            }
432            Request::StandardResponseSchema(req) => {
433                let value = standard::response_schema::execute_jq(executor, req, jq, agent_arguments).await?;
434                Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
435            }
436            Request::SwissSystem(req) => {
437                let want_streaming = req
438                    .dangerous_advanced
439                    .as_ref()
440                    .and_then(|a| a.stream)
441                    .unwrap_or(false);
442                if want_streaming {
443                    let inner = swiss_system::execute_streaming_jq(executor, req, jq, agent_arguments).await?;
444                    Box::pin(inner)
445                } else {
446                    let value = swiss_system::execute_jq(executor, req, jq, agent_arguments).await?;
447                    Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
448                }
449            }
450            Request::SwissSystemRequestSchema(req) => {
451                let value = swiss_system::request_schema::execute_jq(executor, req, jq, agent_arguments).await?;
452                Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
453            }
454            Request::SwissSystemResponseSchema(req) => {
455                let value = swiss_system::response_schema::execute_jq(executor, req, jq, agent_arguments).await?;
456                Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
457            }
458        };
459    Ok(stream)
460}