Skip to main content

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

1//! `functions execute standard` — async handler stub.
2
3use crate::agent::completions::message::Message;  // unused placeholder to keep imports tidy
4use crate::cli::command::CommandRequest;
5use crate::functions::expression::InputValue;
6use super::{FunctionArgs, FunctionSpec, ProfileArgs, ProfileSpec};
7
8#[allow(dead_code)]
9type _UnusedMessage = Message;
10
11#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
12#[schemars(rename = "cli.command.functions.execute.standard.Request")]
13pub struct Request {
14    pub path_type: Path,
15    pub function: FunctionSpec,
16    pub profile: ProfileSpec,
17    pub input: RequestInput,
18    pub continuation: Option<String>,
19    pub retry_token: Option<String>,
20    pub split: bool,
21    pub invert: bool,
22    pub dangerous_advanced: Option<RequestDangerousAdvanced>,
23    #[serde(flatten)]
24    pub base: crate::cli::command::RequestBase,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
28#[schemars(rename = "cli.command.functions.execute.standard.Path")]
29pub enum Path {
30    #[serde(rename = "functions/execute/standard")]
31    FunctionsExecuteStandard,
32}
33
34#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
35#[schemars(rename = "cli.command.functions.execute.standard.RequestInput")]
36pub enum RequestInput {
37    #[schemars(title = "Inline")]
38    Inline(InputValue),
39    #[schemars(title = "File")]
40    File(std::path::PathBuf),
41    #[schemars(title = "PythonInline")]
42    PythonInline(String),
43    #[schemars(title = "PythonFile")]
44    PythonFile(std::path::PathBuf),
45}
46
47impl RequestInput {
48    fn push_flags(&self, out: &mut Vec<String>) {
49        match self {
50            RequestInput::Inline(v) => {
51                out.push("--input-inline".to_string());
52                out.push(serde_json::to_string(v).expect("input serializes"));
53            }
54            RequestInput::File(p) => {
55                out.push("--input-file".to_string());
56                out.push(p.to_string_lossy().into_owned());
57            }
58            RequestInput::PythonInline(code) => {
59                out.push("--input-python-inline".to_string());
60                out.push(code.clone());
61            }
62            RequestInput::PythonFile(p) => {
63                out.push("--input-python-file".to_string());
64                out.push(p.to_string_lossy().into_owned());
65            }
66        }
67    }
68}
69
70impl CommandRequest for Request {
71    fn into_command(&self) -> Vec<String> {
72        let mut argv = vec![
73            "functions".to_string(),
74            "execute".to_string(),
75            "standard".to_string(),
76        ];
77        self.function.push_flags(&mut argv);
78        self.profile.push_flags(&mut argv);
79        self.input.push_flags(&mut argv);
80        if let Some(c) = &self.continuation {
81            argv.push("--continuation".to_string());
82            argv.push(c.clone());
83        }
84        if let Some(t) = &self.retry_token {
85            argv.push("--retry-token".to_string());
86            argv.push(t.clone());
87        }
88        if self.split {
89            argv.push("--split".to_string());
90        }
91        if self.invert {
92            argv.push("--invert".to_string());
93        }
94        if let Some(advanced) = &self.dangerous_advanced {
95            argv.push("--dangerous-advanced".to_string());
96            argv.push(
97                serde_json::to_string(advanced)
98                    .expect("RequestDangerousAdvanced serializes"),
99            );
100        }
101        self.base.push_flags(&mut argv);
102        argv
103    }
104
105    fn request_base(&self) -> &crate::cli::command::RequestBase {
106        &self.base
107    }
108
109    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
110        Some(&mut self.base)
111    }
112}
113
114#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
115#[schemars(rename = "cli.command.functions.execute.standard.RequestDangerousAdvanced")]
116pub struct RequestDangerousAdvanced {
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    #[schemars(extend("omitempty" = true))]
119    pub stream: Option<bool>,
120    /// Deterministic seed for downstream mock agents. Forwarded
121    /// to every per-task `AgentCompletionCreateParams.seed`.
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    #[schemars(extend("omitempty" = true))]
124    pub seed: Option<i64>,
125}
126
127#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
128#[serde(untagged)]
129#[schemars(rename = "cli.command.functions.execute.standard.ResponseItem")]
130pub enum ResponseItem {
131    #[schemars(title = "Chunk")]
132    Chunk(crate::functions::executions::response::streaming::FunctionExecutionChunk),
133    #[schemars(title = "Id")]
134    Id(String),
135}
136
137/// Non-chunk variant of [`ResponseItem`]. Returned by the unary `execute`
138/// path (with `dangerous_advanced.stream` cleared) when the cli emits a
139/// single bare id string.
140pub type Response = String;
141
142/// Exactly-one-of `--input-inline | --input-file | --input-python-inline
143/// | --input-python-file`. Scoped to its own `#[group]` annotation on a
144/// dedicated sub-struct so the `required = true, multiple = false`
145/// enforcement only applies to these fields — hoisting the annotation
146/// to the outer [`Args`] would pull every field into the "input_group"
147/// group via clap derive's default-group rule. Mirrors the
148/// `super::{FunctionArgs, ProfileArgs}` pattern.
149#[derive(clap::Args)]
150#[group(id = "input_group", required = true, multiple = false)]
151pub struct InputArgs {
152    /// Inline JSON input value.
153    #[arg(long, group = "input_group")]
154    pub input_inline: Option<String>,
155    /// Path to a JSON file containing the input value.
156    #[arg(long, group = "input_group")]
157    pub input_file: Option<std::path::PathBuf>,
158    /// Inline Python that produces the input value.
159    #[arg(long, group = "input_group")]
160    pub input_python_inline: Option<String>,
161    /// Path to a Python file that produces the input value.
162    #[arg(long, group = "input_group")]
163    pub input_python_file: Option<std::path::PathBuf>,
164}
165
166#[derive(clap::Args)]
167pub struct Args {
168    /// Exactly one of `--function`, `--function-inline`,
169    /// `--function-file`, `--function-python-inline`,
170    /// `--function-python-file`.
171    #[command(flatten)]
172    pub function: FunctionArgs,
173    /// Exactly one of `--profile`, `--profile-inline`,
174    /// `--profile-file`, `--profile-python-inline`,
175    /// `--profile-python-file`.
176    #[command(flatten)]
177    pub profile: ProfileArgs,
178    /// Exactly one of `--input-inline`, `--input-file`,
179    /// `--input-python-inline`, `--input-python-file`.
180    #[command(flatten)]
181    pub input: InputArgs,
182    /// Continuation token from a previous response.
183    #[arg(long)]
184    pub continuation: Option<String>,
185    /// Retry token from a previous execution.
186    #[arg(long)]
187    pub retry_token: Option<String>,
188    /// Treat input as an array and execute once per element.
189    #[arg(long)]
190    pub split: bool,
191    /// Invert outputs after expressions evaluate.
192    #[arg(long)]
193    pub invert: bool,
194    /// Advanced opt-in flags as inline JSON.
195    #[arg(long)]
196    pub dangerous_advanced: Option<String>,
197    #[command(flatten)]
198    pub base: crate::cli::command::RequestBaseArgs,
199}
200
201#[derive(clap::Args)]
202#[command(args_conflicts_with_subcommands = true)]
203pub struct Command {
204    #[command(flatten)]
205    pub args: Args,
206    #[command(subcommand)]
207    pub schema: Option<Schema>,
208}
209
210#[derive(clap::Subcommand)]
211pub enum Schema {
212    /// Emit the JSON Schema for this leaf's `Request` type and exit.
213    RequestSchema(request_schema::Args),
214    /// Emit the JSON Schema for this leaf's `Response` type and exit.
215    ResponseSchema(response_schema::Args),
216}
217
218impl TryFrom<Args> for Request {
219    type Error = crate::cli::command::FromArgsError;
220    fn try_from(args: Args) -> Result<Self, Self::Error> {
221        let function = FunctionSpec::try_from(args.function)?;
222        let profile = ProfileSpec::try_from(args.profile)?;
223        let input = if let Some(s) = args.input.input_inline {
224            let mut de = serde_json::Deserializer::from_str(&s);
225            let v = serde_path_to_error::deserialize(&mut de)
226                .map_err(|e| crate::cli::command::FromArgsError::json("input_inline", e))?;
227            RequestInput::Inline(v)
228        } else if let Some(p) = args.input.input_file {
229            RequestInput::File(p)
230        } else if let Some(s) = args.input.input_python_inline {
231            RequestInput::PythonInline(s)
232        } else {
233            RequestInput::PythonFile(args.input.input_python_file.unwrap())
234        };
235        let dangerous_advanced: Option<RequestDangerousAdvanced> =
236            if let Some(s) = args.dangerous_advanced {
237                let mut de = serde_json::Deserializer::from_str(&s);
238                let v = serde_path_to_error::deserialize(&mut de)
239                    .map_err(|e| crate::cli::command::FromArgsError::json("dangerous_advanced", e))?;
240                Some(v)
241            } else {
242                None
243            };
244        Ok(Self { path_type: Path::FunctionsExecuteStandard,
245            function,
246            profile,
247            input,
248            continuation: args.continuation,
249            retry_token: args.retry_token,
250            split: args.split,
251            invert: args.invert,
252            dangerous_advanced,
253            base: args.base.into(),
254        })
255    }
256}
257
258#[cfg(feature = "cli-executor")]
259pub async fn execute_streaming<E: crate::cli::command::CommandExecutor>(
260    executor: &E,
261    mut request: Request,
262
263        agent_arguments: Option<&crate::cli::command::AgentArguments>,
264    ) -> Result<E::Stream<ResponseItem>, E::Error> {
265    request.base.clear_transform();
266    let mut advanced = request.dangerous_advanced.unwrap_or_default();
267    advanced.stream = Some(true);
268    request.dangerous_advanced = Some(advanced);
269    executor.execute(request, agent_arguments).await
270}
271
272#[cfg(feature = "cli-executor")]
273pub async fn execute_streaming_transform<E: crate::cli::command::CommandExecutor>(
274    executor: &E,
275    mut request: Request,
276    transform: crate::cli::command::Transform,
277
278        agent_arguments: Option<&crate::cli::command::AgentArguments>,
279    ) -> Result<E::Stream<serde_json::Value>, E::Error> {
280    request.base.set_transform(transform);
281    let mut advanced = request.dangerous_advanced.unwrap_or_default();
282    advanced.stream = Some(true);
283    request.dangerous_advanced = Some(advanced);
284    executor.execute(request, agent_arguments).await
285}
286
287#[cfg(feature = "cli-executor")]
288pub async fn execute<E: crate::cli::command::CommandExecutor>(
289    executor: &E,
290    mut request: Request,
291
292        agent_arguments: Option<&crate::cli::command::AgentArguments>,
293    ) -> Result<Response, E::Error> {
294    request.base.clear_transform();
295    if let Some(advanced) = request.dangerous_advanced.as_mut() {
296        advanced.stream = None;
297    }
298    executor.execute_one(request, agent_arguments).await
299}
300
301#[cfg(feature = "cli-executor")]
302pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
303    executor: &E,
304    mut request: Request,
305    transform: crate::cli::command::Transform,
306
307        agent_arguments: Option<&crate::cli::command::AgentArguments>,
308    ) -> Result<serde_json::Value, E::Error> {
309    request.base.set_transform(transform);
310    if let Some(advanced) = request.dangerous_advanced.as_mut() {
311        advanced.stream = None;
312    }
313    executor.execute_one(request, agent_arguments).await
314}
315
316#[cfg(feature = "mcp")]
317impl crate::cli::command::CommandResponse for ResponseItem {
318    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
319        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
320    }
321}
322
323pub mod request_schema;
324
325
326pub mod response_schema;