Skip to main content

objectiveai_sdk/cli/command/agents/spawn/
mod.rs

1//! `agents spawn` — async handler stub.
2
3use crate::agent::InlineAgentBaseWithFallbacksOrRemoteCommitOptional;
4use crate::agent::completions::message::Message;
5use crate::cli::command::CommandRequest;
6
7#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
8#[schemars(rename = "cli.command.agents.spawn.Request")]
9pub struct Request {
10    pub path_type: Path,
11    pub prompt: RequestPrompt,
12    pub agent: AgentSpec,
13    pub seed: Option<i64>,
14    pub dangerous_advanced: Option<RequestDangerousAdvanced>,
15    pub jq: Option<String>,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
19#[schemars(rename = "cli.command.agents.spawn.Path")]
20pub enum Path {
21    #[serde(rename = "agents/spawn")]
22    AgentsSpawn,
23}
24
25/// CLI-surface form for the `--agent` / `--agent-inline` argument: either
26/// a fully resolved inline-or-remote spec, or a bare favorite name that
27/// the CLI resolves to one of those at handler time. Untagged: an inline
28/// agent object or a remote-path object deserializes into `Resolved`; a
29/// bare JSON string lands on `Favorite`.
30#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
31#[serde(untagged)]
32#[schemars(rename = "cli.command.agents.spawn.AgentSpec")]
33pub enum AgentSpec {
34    #[schemars(title = "Resolved")]
35    Resolved(InlineAgentBaseWithFallbacksOrRemoteCommitOptional),
36    #[schemars(title = "Favorite")]
37    Favorite(String),
38}
39
40#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
41#[schemars(rename = "cli.command.agents.spawn.RequestPrompt")]
42pub enum RequestPrompt {
43    #[schemars(title = "Inline")]
44    Inline(Vec<Message>),
45    #[schemars(title = "Simple")]
46    Simple(String),
47    #[schemars(title = "File")]
48    File(std::path::PathBuf),
49    #[schemars(title = "PythonInline")]
50    PythonInline(String),
51    #[schemars(title = "PythonFile")]
52    PythonFile(std::path::PathBuf),
53}
54
55impl RequestPrompt {
56    fn push_flags(&self, out: &mut Vec<String>) {
57        match self {
58            RequestPrompt::Inline(msgs) => {
59                out.push("--inline".to_string());
60                out.push(
61                    serde_json::to_string(msgs).expect("Vec<Message> serializes"),
62                );
63            }
64            RequestPrompt::Simple(s) => {
65                out.push("--simple".to_string());
66                out.push(s.clone());
67            }
68            RequestPrompt::File(p) => {
69                out.push("--file".to_string());
70                out.push(p.to_string_lossy().into_owned());
71            }
72            RequestPrompt::PythonInline(code) => {
73                out.push("--python-inline".to_string());
74                out.push(code.clone());
75            }
76            RequestPrompt::PythonFile(p) => {
77                out.push("--python-file".to_string());
78                out.push(p.to_string_lossy().into_owned());
79            }
80        }
81    }
82}
83
84impl CommandRequest for Request {
85    fn into_command(&self) -> Vec<String> {
86        let mut argv = vec!["agents".to_string(), "spawn".to_string()];
87        self.prompt.push_flags(&mut argv);
88        // The cli's `AgentArg` (from `define_inline_or_ref!`) accepts
89        // either `--agent <REFERENCE>` (a `FavoriteRef` wire form, then
90        // resolved to the `Remote` variant) or `--agent-inline <JSON>`
91        // (deserialized directly into the SDK type). We always emit the
92        // inline form because the Request already holds the resolved
93        // typed value — the cli's resolve hits the inline branch and
94        // round-trips identically for both Inline and Remote variants.
95        argv.push("--agent-inline".to_string());
96        argv.push(
97            serde_json::to_string(&self.agent).expect("AgentSpec serializes"),
98        );
99        if let Some(seed) = self.seed {
100            argv.push("--seed".to_string());
101            argv.push(seed.to_string());
102        }
103        if let Some(advanced) = &self.dangerous_advanced {
104            argv.push("--dangerous-advanced".to_string());
105            argv.push(
106                serde_json::to_string(advanced)
107                    .expect("RequestDangerousAdvanced serializes"),
108            );
109        }
110        if let Some(jq) = &self.jq {
111            argv.push("--jq".to_string());
112            argv.push(jq.clone());
113        }
114        argv
115    }
116}
117
118#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
119#[schemars(rename = "cli.command.agents.spawn.RequestDangerousAdvanced")]
120pub struct RequestDangerousAdvanced {
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    #[schemars(extend("omitempty" = true))]
123    pub stream: Option<bool>,
124}
125
126#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
127#[serde(untagged)]
128#[schemars(rename = "cli.command.agents.spawn.ResponseItem")]
129pub enum ResponseItem {
130    #[schemars(title = "Chunk")]
131    Chunk(crate::agent::completions::response::streaming::AgentCompletionChunk),
132    #[schemars(title = "Id")]
133    Id(String),
134}
135
136/// Non-chunk variant of [`ResponseItem`]. Returned by the unary `execute`
137/// path (with `dangerous_advanced.stream` cleared) when the cli emits a
138/// single bare id string.
139pub type Response = String;
140
141#[derive(clap::Args)]
142pub struct Args {
143    #[command(flatten)]
144    pub prompt: PromptArgs,
145    #[command(flatten)]
146    pub agent: AgentArgs,
147    /// Seed for deterministic mock responses.
148    #[arg(long)]
149    pub seed: Option<i64>,
150    /// Raw JSON for `RequestDangerousAdvanced` (e.g. `{"stream":true}`).
151    #[arg(long)]
152    pub dangerous_advanced: Option<String>,
153    /// jq filter applied to the JSON output.
154    #[arg(long)]
155    pub jq: Option<String>,
156}
157
158#[derive(clap::Args)]
159#[group(required = true, multiple = false)]
160pub struct PromptArgs {
161    /// Plain text — becomes one user message.
162    #[arg(long)]
163    pub simple: Option<String>,
164    /// Inline JSON messages array.
165    #[arg(long)]
166    pub inline: Option<String>,
167    /// Path to a JSON file containing the messages array.
168    #[arg(long)]
169    pub file: Option<std::path::PathBuf>,
170    /// Inline Python code that produces the messages array.
171    #[arg(long)]
172    pub python_inline: Option<String>,
173    /// Path to a Python file that produces the messages array.
174    #[arg(long)]
175    pub python_file: Option<std::path::PathBuf>,
176}
177
178#[derive(clap::Args)]
179#[group(required = true, multiple = false)]
180pub struct AgentArgs {
181    /// Favorite-ref or remote-path string.
182    #[arg(long)]
183    pub agent: Option<String>,
184    /// Inline JSON for the full agent definition.
185    #[arg(long)]
186    pub agent_inline: Option<String>,
187}
188
189#[derive(clap::Args)]
190#[command(args_conflicts_with_subcommands = true)]
191pub struct Command {
192    #[command(flatten)]
193    pub args: Args,
194    #[command(subcommand)]
195    pub schema: Option<Schema>,
196}
197
198#[derive(clap::Subcommand)]
199pub enum Schema {
200    /// Emit the JSON Schema for this leaf's `Request` type and exit.
201    RequestSchema(request_schema::Args),
202    /// Emit the JSON Schema for this leaf's `Response` type and exit.
203    ResponseSchema(response_schema::Args),
204}
205
206impl TryFrom<Args> for Request {
207    type Error = crate::cli::command::FromArgsError;
208    fn try_from(args: Args) -> Result<Self, Self::Error> {
209        let prompt = if let Some(s) = args.prompt.simple {
210            RequestPrompt::Simple(s)
211        } else if let Some(s) = args.prompt.inline {
212            let mut de = serde_json::Deserializer::from_str(&s);
213            let v = serde_path_to_error::deserialize(&mut de).map_err(|source| {
214                crate::cli::command::FromArgsError {
215                    field: "inline",
216                    source: source.into(),
217                }
218            })?;
219            RequestPrompt::Inline(v)
220        } else if let Some(p) = args.prompt.file {
221            RequestPrompt::File(p)
222        } else if let Some(s) = args.prompt.python_inline {
223            RequestPrompt::PythonInline(s)
224        } else {
225            RequestPrompt::PythonFile(args.prompt.python_file.unwrap())
226        };
227        let agent = if let Some(s) = args.agent.agent_inline {
228            let mut de = serde_json::Deserializer::from_str(&s);
229            serde_path_to_error::deserialize(&mut de).map_err(|source| {
230                crate::cli::command::FromArgsError {
231                    field: "agent_inline",
232                    source: source.into(),
233                }
234            })?
235        } else {
236            AgentSpec::Favorite(args.agent.agent.unwrap())
237        };
238        let dangerous_advanced = if let Some(s) = args.dangerous_advanced {
239            let mut de = serde_json::Deserializer::from_str(&s);
240            let v = serde_path_to_error::deserialize(&mut de).map_err(|source| {
241                crate::cli::command::FromArgsError {
242                    field: "dangerous_advanced",
243                    source: source.into(),
244                }
245            })?;
246            Some(v)
247        } else {
248            None
249        };
250        Ok(Self { path_type: Path::AgentsSpawn,
251            prompt,
252            agent,
253            seed: args.seed,
254            dangerous_advanced,
255            jq: args.jq,
256        })
257    }
258}
259
260#[cfg(feature = "cli-executor")]
261pub async fn execute_streaming<E: crate::cli::command::CommandExecutor>(
262    executor: &E,
263    mut request: Request,
264
265        agent_arguments: Option<&crate::cli::command::AgentArguments>,
266    ) -> Result<E::Stream<ResponseItem>, E::Error> {
267    request.jq = None;
268    let mut advanced = request.dangerous_advanced.unwrap_or_default();
269    advanced.stream = Some(true);
270    request.dangerous_advanced = Some(advanced);
271    executor.execute(request, agent_arguments).await
272}
273
274#[cfg(feature = "cli-executor")]
275pub async fn execute_streaming_jq<E: crate::cli::command::CommandExecutor>(
276    executor: &E,
277    mut request: Request,
278    jq: String,
279
280        agent_arguments: Option<&crate::cli::command::AgentArguments>,
281    ) -> Result<E::Stream<serde_json::Value>, E::Error> {
282    request.jq = Some(jq);
283    let mut advanced = request.dangerous_advanced.unwrap_or_default();
284    advanced.stream = Some(true);
285    request.dangerous_advanced = Some(advanced);
286    executor.execute(request, agent_arguments).await
287}
288
289#[cfg(feature = "cli-executor")]
290pub async fn execute<E: crate::cli::command::CommandExecutor>(
291    executor: &E,
292    mut request: Request,
293
294        agent_arguments: Option<&crate::cli::command::AgentArguments>,
295    ) -> Result<Response, E::Error> {
296    request.jq = None;
297    if let Some(advanced) = request.dangerous_advanced.as_mut() {
298        advanced.stream = None;
299    }
300    executor.execute_one(request, agent_arguments).await
301}
302
303#[cfg(feature = "cli-executor")]
304pub async fn execute_jq<E: crate::cli::command::CommandExecutor>(
305    executor: &E,
306    mut request: Request,
307    jq: String,
308
309        agent_arguments: Option<&crate::cli::command::AgentArguments>,
310    ) -> Result<serde_json::Value, E::Error> {
311    request.jq = Some(jq);
312    if let Some(advanced) = request.dangerous_advanced.as_mut() {
313        advanced.stream = None;
314    }
315    executor.execute_one(request, agent_arguments).await
316}
317
318#[cfg(feature = "mcp")]
319impl crate::cli::command::CommandResponse for ResponseItem {
320    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
321        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
322    }
323}
324
325pub mod request_schema;
326
327
328pub mod response_schema;