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 split: bool,
20    pub invert: bool,
21    pub dangerous_advanced: Option<RequestDangerousAdvanced>,
22    #[serde(flatten)]
23    pub base: crate::cli::command::RequestBase,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
27#[schemars(rename = "cli.command.functions.execute.standard.Path")]
28pub enum Path {
29    #[serde(rename = "functions/execute/standard")]
30    FunctionsExecuteStandard,
31}
32
33#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
34#[schemars(rename = "cli.command.functions.execute.standard.RequestInput")]
35pub enum RequestInput {
36    #[schemars(title = "Inline")]
37    Inline(InputValue),
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 RequestInput {
47    fn push_flags(&self, out: &mut Vec<String>) {
48        match self {
49            RequestInput::Inline(v) => {
50                out.push("--input-inline".to_string());
51                out.push(serde_json::to_string(v).expect("input serializes"));
52            }
53            RequestInput::File(p) => {
54                out.push("--input-file".to_string());
55                out.push(p.to_string_lossy().into_owned());
56            }
57            RequestInput::PythonInline(code) => {
58                out.push("--input-python-inline".to_string());
59                out.push(code.clone());
60            }
61            RequestInput::PythonFile(p) => {
62                out.push("--input-python-file".to_string());
63                out.push(p.to_string_lossy().into_owned());
64            }
65        }
66    }
67}
68
69impl CommandRequest for Request {
70    fn request_base(&self) -> &crate::cli::command::RequestBase {
71        &self.base
72    }
73
74    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
75        Some(&mut self.base)
76    }
77}
78
79#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
80#[schemars(rename = "cli.command.functions.execute.standard.RequestDangerousAdvanced")]
81pub struct RequestDangerousAdvanced {
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    #[schemars(extend("omitempty" = true))]
84    pub stream: Option<bool>,
85    /// Deterministic seed for downstream mock agents. Forwarded
86    /// to every per-task `AgentCompletionCreateParams.seed`.
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    #[schemars(extend("omitempty" = true))]
89    pub seed: Option<i64>,
90}
91
92/// A unique agent instance participating in this execution, announced
93/// exactly once — right after its instance lock is acquired (the same
94/// moment `agents spawn` announces its own hierarchy). The constant
95/// `type:"agent_instance_hierarchy"` discriminator disambiguates this
96/// variant inside the untagged [`ResponseItem`] union, mirroring
97/// `type:"mcp"` on `plugins run`'s `Mcp`.
98#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
99#[schemars(rename = "cli.command.functions.execute.standard.AgentInstanceHierarchy")]
100pub struct AgentInstanceHierarchy {
101    pub r#type: AgentInstanceHierarchyType,
102    pub agent_instance_hierarchy: String,
103}
104
105/// Single-variant discriminator for [`AgentInstanceHierarchy`]'s
106/// `type` field. Always `"agent_instance_hierarchy"` on the wire.
107#[derive(
108    Debug,
109    Clone,
110    Copy,
111    PartialEq,
112    Eq,
113    serde::Serialize,
114    serde::Deserialize,
115    schemars::JsonSchema,
116)]
117#[serde(rename_all = "snake_case")]
118#[schemars(rename = "cli.command.functions.execute.standard.AgentInstanceHierarchyType")]
119pub enum AgentInstanceHierarchyType {
120    AgentInstanceHierarchy,
121}
122
123#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
124#[serde(untagged)]
125#[schemars(rename = "cli.command.functions.execute.standard.ResponseItem")]
126pub enum ResponseItem {
127    // Placement above `Chunk` is load-bearing: serde untagged tries
128    // variants in source order, and the constant discriminator must
129    // win before the all-optional chunk object could absorb it.
130    #[schemars(title = "AgentInstanceHierarchy")]
131    AgentInstanceHierarchy(AgentInstanceHierarchy),
132    #[schemars(title = "Chunk")]
133    Chunk(crate::functions::executions::response::streaming::FunctionExecutionChunk),
134    #[schemars(title = "Id")]
135    Id(String),
136}
137
138/// Non-chunk variant of [`ResponseItem`]. Returned by the unary `execute`
139/// path (with `dangerous_advanced.stream` cleared) when the cli emits a
140/// single bare id string.
141pub type Response = String;
142
143/// Exactly-one-of `--input-inline | --input-file | --input-python-inline
144/// | --input-python-file`. Scoped to its own `#[group]` annotation on a
145/// dedicated sub-struct so the `required = true, multiple = false`
146/// enforcement only applies to these fields — hoisting the annotation
147/// to the outer [`Args`] would pull every field into the "input_group"
148/// group via clap derive's default-group rule. Mirrors the
149/// `super::{FunctionArgs, ProfileArgs}` pattern.
150#[derive(clap::Args)]
151#[group(id = "input_group", required = true, multiple = false)]
152pub struct InputArgs {
153    /// Inline JSON input value.
154    #[arg(long, group = "input_group")]
155    pub input_inline: Option<String>,
156    /// Path to a JSON file containing the input value.
157    #[arg(long, group = "input_group")]
158    pub input_file: Option<std::path::PathBuf>,
159    /// Inline Python that produces the input value.
160    #[arg(long, group = "input_group")]
161    pub input_python_inline: Option<String>,
162    /// Path to a Python file that produces the input value.
163    #[arg(long, group = "input_group")]
164    pub input_python_file: Option<std::path::PathBuf>,
165}
166
167#[derive(clap::Args)]
168pub struct Args {
169    /// Exactly one of `--function`, `--function-inline`,
170    /// `--function-file`, `--function-python-inline`,
171    /// `--function-python-file`.
172    #[command(flatten)]
173    pub function: FunctionArgs,
174    /// Exactly one of `--profile`, `--profile-inline`,
175    /// `--profile-file`, `--profile-python-inline`,
176    /// `--profile-python-file`.
177    #[command(flatten)]
178    pub profile: ProfileArgs,
179    /// Exactly one of `--input-inline`, `--input-file`,
180    /// `--input-python-inline`, `--input-python-file`.
181    #[command(flatten)]
182    pub input: InputArgs,
183    /// Continuation token from a previous response.
184    #[arg(long)]
185    pub continuation: Option<String>,
186    /// Treat input as an array and execute once per element.
187    #[arg(long)]
188    pub split: bool,
189    /// Invert outputs after expressions evaluate.
190    #[arg(long)]
191    pub invert: bool,
192    /// Advanced opt-in flags as inline JSON.
193    #[arg(long)]
194    pub dangerous_advanced: Option<String>,
195    #[command(flatten)]
196    pub base: crate::cli::command::RequestBaseArgs,
197}
198
199#[derive(clap::Args)]
200#[command(args_conflicts_with_subcommands = true)]
201pub struct Command {
202    #[command(flatten)]
203    pub args: Args,
204    #[command(subcommand)]
205    pub schema: Option<Schema>,
206}
207
208#[derive(clap::Subcommand)]
209pub enum Schema {
210    /// Emit the JSON Schema for this leaf's `Request` type and exit.
211    RequestSchema(request_schema::Args),
212    /// Emit the JSON Schema for this leaf's `Response` type and exit.
213    ResponseSchema(response_schema::Args),
214}
215
216impl TryFrom<Args> for Request {
217    type Error = crate::cli::command::FromArgsError;
218    fn try_from(args: Args) -> Result<Self, Self::Error> {
219        let function = FunctionSpec::try_from(args.function)?;
220        let profile = ProfileSpec::try_from(args.profile)?;
221        let input = if let Some(s) = args.input.input_inline {
222            let mut de = serde_json::Deserializer::from_str(&s);
223            let v = serde_path_to_error::deserialize(&mut de)
224                .map_err(|e| crate::cli::command::FromArgsError::json("input_inline", e))?;
225            RequestInput::Inline(v)
226        } else if let Some(p) = args.input.input_file {
227            RequestInput::File(p)
228        } else if let Some(s) = args.input.input_python_inline {
229            RequestInput::PythonInline(s)
230        } else {
231            RequestInput::PythonFile(args.input.input_python_file.unwrap())
232        };
233        let dangerous_advanced: Option<RequestDangerousAdvanced> =
234            if let Some(s) = args.dangerous_advanced {
235                let mut de = serde_json::Deserializer::from_str(&s);
236                let v = serde_path_to_error::deserialize(&mut de)
237                    .map_err(|e| crate::cli::command::FromArgsError::json("dangerous_advanced", e))?;
238                Some(v)
239            } else {
240                None
241            };
242        Ok(Self { path_type: Path::FunctionsExecuteStandard,
243            function,
244            profile,
245            input,
246            continuation: args.continuation,
247            split: args.split,
248            invert: args.invert,
249            dangerous_advanced,
250            base: args.base.into(),
251        })
252    }
253}
254
255#[cfg(feature = "cli-executor")]
256pub async fn execute_streaming<E: crate::cli::command::CommandExecutor>(
257    executor: &E,
258    mut request: Request,
259
260        agent_arguments: Option<&crate::cli::command::AgentArguments>,
261    ) -> Result<E::Stream<ResponseItem>, E::Error> {
262    request.base.clear_transform();
263    let mut advanced = request.dangerous_advanced.unwrap_or_default();
264    advanced.stream = Some(true);
265    request.dangerous_advanced = Some(advanced);
266    executor.execute(request, agent_arguments).await
267}
268
269#[cfg(feature = "cli-executor")]
270pub async fn execute_streaming_transform<E: crate::cli::command::CommandExecutor>(
271    executor: &E,
272    mut request: Request,
273    transform: crate::cli::command::Transform,
274
275        agent_arguments: Option<&crate::cli::command::AgentArguments>,
276    ) -> Result<E::Stream<serde_json::Value>, E::Error> {
277    request.base.set_transform(transform);
278    let mut advanced = request.dangerous_advanced.unwrap_or_default();
279    advanced.stream = Some(true);
280    request.dangerous_advanced = Some(advanced);
281    executor.execute(request, agent_arguments).await
282}
283
284#[cfg(feature = "cli-executor")]
285pub async fn execute<E: crate::cli::command::CommandExecutor>(
286    executor: &E,
287    mut request: Request,
288
289        agent_arguments: Option<&crate::cli::command::AgentArguments>,
290    ) -> Result<Response, E::Error> {
291    request.base.clear_transform();
292    if let Some(advanced) = request.dangerous_advanced.as_mut() {
293        advanced.stream = None;
294    }
295    executor.execute_one(request, agent_arguments).await
296}
297
298#[cfg(feature = "cli-executor")]
299pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
300    executor: &E,
301    mut request: Request,
302    transform: crate::cli::command::Transform,
303
304        agent_arguments: Option<&crate::cli::command::AgentArguments>,
305    ) -> Result<serde_json::Value, E::Error> {
306    request.base.set_transform(transform);
307    if let Some(advanced) = request.dangerous_advanced.as_mut() {
308        advanced.stream = None;
309    }
310    executor.execute_one(request, agent_arguments).await
311}
312
313#[cfg(feature = "mcp")]
314impl crate::cli::command::CommandResponse for ResponseItem {
315    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
316        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
317    }
318}
319
320pub mod request_schema;
321
322pub mod response_schema;
323
324/// One `/listen` broadcast run of `functions execute standard` in its unary
325/// form (the plain `execute`): the actual [`Request`], the
326/// producer's
327/// [`AgentArguments`](crate::cli::command::AgentArguments), and the
328/// unary response future. See [`crate::cli::websocket_listener`].
329#[cfg(feature = "cli-listener")]
330pub struct ListenerExecution {
331    pub request: Request,
332    pub agent_arguments: crate::cli::command::AgentArguments,
333    pub response: crate::cli::websocket_listener::UnaryResponse<Response>,
334}
335
336/// One `/listen` broadcast run of `functions execute standard` in its
337/// streaming form (`execute_streaming` — the request set
338/// `dangerous_advanced.stream: true`): the actual [`Request`], the
339/// producer's
340/// [`AgentArguments`](crate::cli::command::AgentArguments), and the
341/// response-item stream. See [`crate::cli::websocket_listener`].
342#[cfg(feature = "cli-listener")]
343pub struct ListenerExecutionStreaming {
344    pub request: Request,
345    pub agent_arguments: crate::cli::command::AgentArguments,
346    pub response: crate::cli::websocket_listener::ResponseItemStream<ResponseItem>,
347}
348
349/// This leaf's multiple listener executions — one variant per
350/// execute fn (`Execution` for the plain `execute`, `Streaming`
351/// for `execute_streaming`), discriminated per request off
352/// `dangerous_advanced.stream`. The branch enum's single variant
353/// for this leaf wraps this.
354#[cfg(feature = "cli-listener")]
355pub enum ListenerExecutionVariant {
356    Execution(ListenerExecution),
357    Streaming(ListenerExecutionStreaming),
358}