Skip to main content

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

1//! `agents enqueue` — fire-and-forget into the queue.
2//!
3//! Persists one message into `message_queue` against an agent
4//! instance or tag and returns immediately: no lock race, no spawn
5//! child, no delivery wait (that's `agents message`). With `--key`,
6//! the enqueue is idempotent — any pre-existing row scoped to the
7//! same (target, key) pair is replaced.
8
9use crate::cli::command::CommandRequest;
10use crate::cli::command::agents::message::RequestMessage;
11use crate::cli::command::agents::selector::AgentSelector;
12
13#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
14#[schemars(rename = "cli.command.agents.enqueue.Request")]
15pub struct Request {
16    pub path_type: Path,
17    /// Whose queue the message lands in — an instance hierarchy or
18    /// a tag (parked against the tag NAME; the queue's two-rule
19    /// read predicate resolves BOUND tags to their hierarchy). A
20    /// plain ref has no queue identity and errors.
21    pub agent: AgentSelector,
22    /// Required payload. The queue row carries this exact
23    /// `RichContent`.
24    pub message: RequestMessage,
25    /// Idempotency key, scoped per target: any pre-existing active
26    /// row with the same `(agent_instance_hierarchy, key)` or
27    /// `(agent_tag, key)` pair is deleted before the insert lands.
28    #[serde(default, skip_serializing_if = "Option::is_none")]
29    #[schemars(extend("omitempty" = true))]
30    pub key: Option<String>,
31    #[serde(flatten)]
32    pub base: crate::cli::command::RequestBase,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
36#[schemars(rename = "cli.command.agents.enqueue.Path")]
37pub enum Path {
38    #[serde(rename = "agents/enqueue")]
39    AgentsEnqueue,
40}
41
42impl CommandRequest for Request {
43    fn into_command(&self) -> Vec<String> {
44        let mut argv = vec!["agents".to_string(), "enqueue".to_string()];
45        self.agent.push_flags(&mut argv);
46        self.message.push_flags(&mut argv);
47        if let Some(key) = &self.key {
48            argv.push("--key".to_string());
49            argv.push(key.clone());
50        }
51        self.base.push_flags(&mut argv);
52        argv
53    }
54
55    fn request_base(&self) -> &crate::cli::command::RequestBase {
56        &self.base
57    }
58
59    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
60        Some(&mut self.base)
61    }
62}
63
64/// The freshly parked queue row: its id and the target it's scoped
65/// to (exactly one of `agent_instance_hierarchy` / `agent_tag` is
66/// set).
67#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
68#[schemars(rename = "cli.command.agents.enqueue.Response")]
69pub struct Response {
70    pub id: i64,
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    #[schemars(extend("omitempty" = true))]
73    pub agent_instance_hierarchy: Option<String>,
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    #[schemars(extend("omitempty" = true))]
76    pub agent_tag: Option<String>,
77}
78
79#[derive(clap::Args)]
80pub struct Args {
81    #[command(flatten)]
82    pub agent: crate::cli::command::agents::selector::AgentSelectorArgs,
83    #[command(flatten)]
84    pub message: MessageArgs,
85    /// Idempotency key — existing queue rows scoped to the same
86    /// target (instance hierarchy or tag) AND key are replaced.
87    #[arg(long)]
88    pub key: Option<String>,
89    #[command(flatten)]
90    pub base: crate::cli::command::RequestBaseArgs,
91}
92
93/// Required user-message group. Mirrors `agents message`'s shape:
94/// exactly one of the five flags must be set.
95#[derive(clap::Args)]
96#[group(required = true, multiple = false)]
97pub struct MessageArgs {
98    /// Plain text — becomes one user message.
99    #[arg(long)]
100    pub simple: Option<String>,
101    /// Inline JSON `RichContent`.
102    #[arg(long)]
103    pub inline: Option<String>,
104    /// Path to a JSON file containing the rich content.
105    #[arg(long)]
106    pub file: Option<std::path::PathBuf>,
107    /// Inline Python code that produces the rich content.
108    #[arg(long)]
109    pub python_inline: Option<String>,
110    /// Path to a Python file that produces the rich content.
111    #[arg(long)]
112    pub python_file: Option<std::path::PathBuf>,
113}
114
115#[derive(clap::Args)]
116#[command(args_conflicts_with_subcommands = true)]
117pub struct Command {
118    #[command(flatten)]
119    pub args: Args,
120    #[command(subcommand)]
121    pub schema: Option<Schema>,
122}
123
124#[derive(clap::Subcommand)]
125pub enum Schema {
126    /// Emit the JSON Schema for this leaf's `Request` type and exit.
127    RequestSchema(request_schema::Args),
128    /// Emit the JSON Schema for this leaf's `Response` type and exit.
129    ResponseSchema(response_schema::Args),
130}
131
132impl TryFrom<Args> for Request {
133    type Error = crate::cli::command::FromArgsError;
134    fn try_from(args: Args) -> Result<Self, Self::Error> {
135        let message = if let Some(s) = args.message.simple {
136            RequestMessage::Simple(s)
137        } else if let Some(s) = args.message.inline {
138            let mut de = serde_json::Deserializer::from_str(&s);
139            let v = serde_path_to_error::deserialize(&mut de).map_err(|source| {
140                crate::cli::command::FromArgsError {
141                    field: "inline",
142                    source: source.into(),
143                }
144            })?;
145            RequestMessage::Inline(v)
146        } else if let Some(p) = args.message.file {
147            RequestMessage::File(p)
148        } else if let Some(s) = args.message.python_inline {
149            RequestMessage::PythonInline(s)
150        } else {
151            // Clap `required = true` on `MessageArgs` guarantees
152            // exactly one of the five flags is set.
153            RequestMessage::PythonFile(args.message.python_file.unwrap())
154        };
155        let agent = AgentSelector::try_from(args.agent)?;
156        Ok(Self {
157            path_type: Path::AgentsEnqueue,
158            agent,
159            message,
160            key: args.key,
161            base: args.base.into(),
162        })
163    }
164}
165
166#[cfg(feature = "cli-executor")]
167pub async fn execute<E: crate::cli::command::CommandExecutor>(
168    executor: &E,
169    mut request: Request,
170
171        agent_arguments: Option<&crate::cli::command::AgentArguments>,
172    ) -> Result<Response, E::Error> {
173    request.base.clear_transform();
174    executor.execute_one(request, agent_arguments).await
175}
176
177#[cfg(feature = "cli-executor")]
178pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
179    executor: &E,
180    mut request: Request,
181    transform: crate::cli::command::Transform,
182
183        agent_arguments: Option<&crate::cli::command::AgentArguments>,
184    ) -> Result<serde_json::Value, E::Error> {
185    request.base.set_transform(transform);
186    executor.execute_one(request, agent_arguments).await
187}
188
189#[cfg(feature = "mcp")]
190impl crate::cli::command::CommandResponse for Response {
191    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
192        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
193    }
194}
195
196pub mod request_schema;
197
198pub mod response_schema;