Skip to main content

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

1//! `agents message` — async handler stub.
2
3use crate::agent::completions::message::RichContent;
4use crate::cli::command::CommandRequest;
5
6#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
7#[schemars(rename = "cli.command.agents.message.Request")]
8pub struct Request {
9    pub path_type: Path,
10    /// Lineage prefix to prepend to [`Self::agent_instance`]. When
11    /// `None`, the CLI substitutes its own
12    /// `Config.agent_instance_hierarchy` (the cli's "caller"
13    /// position). Full target lineage is `"{parent}/{instance}"`.
14    pub parent_agent_instance_hierarchy: Option<String>,
15    /// Leaf id of the target agent. Combined with
16    /// [`Self::parent_agent_instance_hierarchy`] (or the cli's
17    /// caller position when that is `None`) to form the full
18    /// hierarchy.
19    pub agent_instance: String,
20    pub message: RequestMessage,
21    pub seed: Option<i64>,
22    pub jq: Option<String>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
26#[schemars(rename = "cli.command.agents.message.Path")]
27pub enum Path {
28    #[serde(rename = "agents/message")]
29    AgentsMessage,
30}
31
32#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
33#[schemars(rename = "cli.command.agents.message.RequestMessage")]
34pub enum RequestMessage {
35    #[schemars(title = "Inline")]
36    Inline(RichContent),
37    #[schemars(title = "Simple")]
38    Simple(String),
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 RequestMessage {
48    fn push_flags(&self, out: &mut Vec<String>) {
49        match self {
50            RequestMessage::Inline(rich) => {
51                out.push("--inline".to_string());
52                out.push(
53                    serde_json::to_string(rich)
54                        .expect("RichContent serializes to JSON cleanly"),
55                );
56            }
57            RequestMessage::Simple(s) => {
58                out.push("--simple".to_string());
59                out.push(s.clone());
60            }
61            RequestMessage::File(p) => {
62                out.push("--file".to_string());
63                out.push(p.to_string_lossy().into_owned());
64            }
65            RequestMessage::PythonInline(code) => {
66                out.push("--python-inline".to_string());
67                out.push(code.clone());
68            }
69            RequestMessage::PythonFile(p) => {
70                out.push("--python-file".to_string());
71                out.push(p.to_string_lossy().into_owned());
72            }
73        }
74    }
75}
76
77impl CommandRequest for Request {
78    fn into_command(&self) -> Vec<String> {
79        let mut argv = vec![
80            "agents".to_string(),
81            "message".to_string(),
82            self.agent_instance.clone(),
83        ];
84        if let Some(parent) = &self.parent_agent_instance_hierarchy {
85            argv.push("--parent-agent-instance-hierarchy".to_string());
86            argv.push(parent.clone());
87        }
88        self.message.push_flags(&mut argv);
89        if let Some(seed) = self.seed {
90            argv.push("--seed".to_string());
91            argv.push(seed.to_string());
92        }
93        if let Some(jq) = &self.jq {
94            argv.push("--jq".to_string());
95            argv.push(jq.clone());
96        }
97        argv
98    }
99}
100
101#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
102#[serde(untagged)]
103#[schemars(rename = "cli.command.agents.message.Response")]
104pub enum Response {
105    #[schemars(title = "Queued")]
106    Queued {
107        agent_instance_hierarchy: String,
108        response_id: String,
109    },
110    #[schemars(title = "Delivered")]
111    Delivered {
112        agent_instance_hierarchy: String,
113    },
114}
115
116#[derive(clap::Args)]
117pub struct Args {
118    /// Leaf id of the target agent. Combined with `--parent` (or
119    /// the cli's own `Config.agent_instance_hierarchy` when
120    /// `--parent` is omitted) to form the full lineage.
121    pub agent_instance: String,
122    /// Optional lineage prefix to prepend to `agent_instance`.
123    /// When omitted, the cli substitutes its own
124    /// `Config.agent_instance_hierarchy`.
125    #[arg(long = "parent-agent-instance-hierarchy")]
126    pub parent_agent_instance_hierarchy: Option<String>,
127    #[command(flatten)]
128    pub message: MessageArgs,
129    /// Seed for deterministic mock responses.
130    #[arg(long)]
131    pub seed: Option<i64>,
132    /// jq filter applied to the JSON output.
133    #[arg(long)]
134    pub jq: Option<String>,
135}
136
137#[derive(clap::Args)]
138#[group(required = true, multiple = false)]
139pub struct MessageArgs {
140    /// Plain text — becomes one user message.
141    #[arg(long)]
142    pub simple: Option<String>,
143    /// Inline JSON `RichContent`.
144    #[arg(long)]
145    pub inline: Option<String>,
146    /// Path to a JSON file containing the rich content.
147    #[arg(long)]
148    pub file: Option<std::path::PathBuf>,
149    /// Inline Python code that produces the rich content.
150    #[arg(long)]
151    pub python_inline: Option<String>,
152    /// Path to a Python file that produces the rich content.
153    #[arg(long)]
154    pub python_file: Option<std::path::PathBuf>,
155}
156
157#[derive(clap::Args)]
158#[command(args_conflicts_with_subcommands = true)]
159pub struct Command {
160    #[command(flatten)]
161    pub args: Args,
162    #[command(subcommand)]
163    pub schema: Option<Schema>,
164}
165
166#[derive(clap::Subcommand)]
167pub enum Schema {
168    /// Emit the JSON Schema for this leaf's `Request` type and exit.
169    RequestSchema(request_schema::Args),
170    /// Emit the JSON Schema for this leaf's `Response` type and exit.
171    ResponseSchema(response_schema::Args),
172}
173
174impl TryFrom<Args> for Request {
175    type Error = crate::cli::command::FromArgsError;
176    fn try_from(args: Args) -> Result<Self, Self::Error> {
177        let message = if let Some(s) = args.message.simple {
178            RequestMessage::Simple(s)
179        } else if let Some(s) = args.message.inline {
180            let mut de = serde_json::Deserializer::from_str(&s);
181            let v = serde_path_to_error::deserialize(&mut de).map_err(|source| {
182                crate::cli::command::FromArgsError {
183                    field: "inline",
184                    source: source.into(),
185                }
186            })?;
187            RequestMessage::Inline(v)
188        } else if let Some(p) = args.message.file {
189            RequestMessage::File(p)
190        } else if let Some(s) = args.message.python_inline {
191            RequestMessage::PythonInline(s)
192        } else {
193            RequestMessage::PythonFile(args.message.python_file.unwrap())
194        };
195        Ok(Self {
196            path_type: Path::AgentsMessage,
197            parent_agent_instance_hierarchy: args.parent_agent_instance_hierarchy,
198            agent_instance: args.agent_instance,
199            message,
200            seed: args.seed,
201            jq: args.jq,
202        })
203    }
204}
205
206#[cfg(feature = "cli-executor")]
207pub async fn execute<E: crate::cli::command::CommandExecutor>(
208    executor: &E,
209    mut request: Request,
210
211        agent_arguments: Option<&crate::cli::command::AgentArguments>,
212    ) -> Result<Response, E::Error> {
213    request.jq = None;
214    executor.execute_one(request, agent_arguments).await
215}
216
217#[cfg(feature = "cli-executor")]
218pub async fn execute_jq<E: crate::cli::command::CommandExecutor>(
219    executor: &E,
220    mut request: Request,
221    jq: String,
222
223        agent_arguments: Option<&crate::cli::command::AgentArguments>,
224    ) -> Result<serde_json::Value, E::Error> {
225    request.jq = Some(jq);
226    executor.execute_one(request, agent_arguments).await
227}
228
229#[cfg(feature = "mcp")]
230impl crate::cli::command::CommandResponse for Response {
231    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
232        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
233    }
234}
235
236pub mod request_schema;
237
238
239pub mod response_schema;