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 into_command(&self) -> Vec<String> {
295        match self {
296            Request::Standard(inner) => inner.into_command(),
297            Request::StandardRequestSchema(inner) => inner.into_command(),
298            Request::StandardResponseSchema(inner) => inner.into_command(),
299            Request::SwissSystem(inner) => inner.into_command(),
300            Request::SwissSystemRequestSchema(inner) => inner.into_command(),
301            Request::SwissSystemResponseSchema(inner) => inner.into_command(),
302        }
303    }
304
305    fn request_base(&self) -> &crate::cli::command::RequestBase {
306        match self {
307            Request::Standard(inner) => inner.request_base(),
308            Request::StandardRequestSchema(inner) => inner.request_base(),
309            Request::StandardResponseSchema(inner) => inner.request_base(),
310            Request::SwissSystem(inner) => inner.request_base(),
311            Request::SwissSystemRequestSchema(inner) => inner.request_base(),
312            Request::SwissSystemResponseSchema(inner) => inner.request_base(),
313        }
314    }
315
316    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
317        match self {
318            Request::Standard(inner) => inner.request_base_mut(),
319            Request::StandardRequestSchema(inner) => inner.request_base_mut(),
320            Request::StandardResponseSchema(inner) => inner.request_base_mut(),
321            Request::SwissSystem(inner) => inner.request_base_mut(),
322            Request::SwissSystemRequestSchema(inner) => inner.request_base_mut(),
323            Request::SwissSystemResponseSchema(inner) => inner.request_base_mut(),
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_transform<E: crate::cli::command::CommandExecutor>(
403    executor: &E,
404    request: Request,
405    transform: crate::cli::command::Transform,
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_transform(executor, req, transform, agent_arguments).await?;
422                    Box::pin(inner)
423                } else {
424                    let value = standard::execute_transform(executor, req, transform, 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_transform(executor, req, transform, 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_transform(executor, req, transform, 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_transform(executor, req, transform, agent_arguments).await?;
444                    Box::pin(inner)
445                } else {
446                    let value = swiss_system::execute_transform(executor, req, transform, 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_transform(executor, req, transform, 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_transform(executor, req, transform, agent_arguments).await?;
456                Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
457            }
458        };
459    Ok(stream)
460}