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