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 request_base(&self) -> &crate::cli::command::RequestBase {
44        &self.base
45    }
46
47    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
48        Some(&mut self.base)
49    }
50}
51
52/// The freshly parked queue row: its id and the target it's scoped
53/// to (exactly one of `agent_instance_hierarchy` / `agent_tag` is
54/// set).
55#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
56#[schemars(rename = "cli.command.agents.enqueue.Response")]
57pub struct Response {
58    pub id: i64,
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    #[schemars(extend("omitempty" = true))]
61    pub agent_instance_hierarchy: Option<String>,
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    #[schemars(extend("omitempty" = true))]
64    pub agent_tag: Option<String>,
65}
66
67#[derive(clap::Args)]
68pub struct Args {
69    #[command(flatten)]
70    pub agent: crate::cli::command::agents::selector::AgentSelectorArgs,
71    #[command(flatten)]
72    pub message: MessageArgs,
73    /// Idempotency key — existing queue rows scoped to the same
74    /// target (instance hierarchy or tag) AND key are replaced.
75    #[arg(long)]
76    pub key: Option<String>,
77    #[command(flatten)]
78    pub base: crate::cli::command::RequestBaseArgs,
79}
80
81/// Required user-message group. Mirrors `agents message`'s shape:
82/// exactly one of the five flags must be set.
83#[derive(clap::Args)]
84#[group(required = true, multiple = false)]
85pub struct MessageArgs {
86    /// Plain text — becomes one user message.
87    #[arg(long)]
88    pub simple: Option<String>,
89    /// Inline JSON `RichContent`.
90    #[arg(long)]
91    pub inline: Option<String>,
92    /// Path to a JSON file containing the rich content.
93    #[arg(long)]
94    pub file: Option<std::path::PathBuf>,
95    /// Inline Python code that produces the rich content.
96    #[arg(long)]
97    pub python_inline: Option<String>,
98    /// Path to a Python file that produces the rich content.
99    #[arg(long)]
100    pub python_file: Option<std::path::PathBuf>,
101}
102
103#[derive(clap::Args)]
104#[command(args_conflicts_with_subcommands = true)]
105pub struct Command {
106    #[command(flatten)]
107    pub args: Args,
108    #[command(subcommand)]
109    pub schema: Option<Schema>,
110}
111
112#[derive(clap::Subcommand)]
113pub enum Schema {
114    /// Emit the JSON Schema for this leaf's `Request` type and exit.
115    RequestSchema(request_schema::Args),
116    /// Emit the JSON Schema for this leaf's `Response` type and exit.
117    ResponseSchema(response_schema::Args),
118}
119
120impl TryFrom<Args> for Request {
121    type Error = crate::cli::command::FromArgsError;
122    fn try_from(args: Args) -> Result<Self, Self::Error> {
123        let message = if let Some(s) = args.message.simple {
124            RequestMessage::Simple(s)
125        } else if let Some(s) = args.message.inline {
126            let mut de = serde_json::Deserializer::from_str(&s);
127            let v = serde_path_to_error::deserialize(&mut de).map_err(|source| {
128                crate::cli::command::FromArgsError {
129                    field: "inline",
130                    source: source.into(),
131                }
132            })?;
133            RequestMessage::Inline(v)
134        } else if let Some(p) = args.message.file {
135            RequestMessage::File(p)
136        } else if let Some(s) = args.message.python_inline {
137            RequestMessage::PythonInline(s)
138        } else {
139            // Clap `required = true` on `MessageArgs` guarantees
140            // exactly one of the five flags is set.
141            RequestMessage::PythonFile(args.message.python_file.unwrap())
142        };
143        let agent = AgentSelector::try_from(args.agent)?;
144        Ok(Self {
145            path_type: Path::AgentsEnqueue,
146            agent,
147            message,
148            key: args.key,
149            base: args.base.into(),
150        })
151    }
152}
153
154#[cfg(feature = "cli-executor")]
155pub async fn execute<E: crate::cli::command::CommandExecutor>(
156    executor: &E,
157    mut request: Request,
158
159        agent_arguments: Option<&crate::cli::command::AgentArguments>,
160    ) -> Result<Response, E::Error> {
161    request.base.clear_transform();
162    executor.execute_one(request, agent_arguments).await
163}
164
165#[cfg(feature = "cli-executor")]
166pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
167    executor: &E,
168    mut request: Request,
169    transform: crate::cli::command::Transform,
170
171        agent_arguments: Option<&crate::cli::command::AgentArguments>,
172    ) -> Result<serde_json::Value, E::Error> {
173    request.base.set_transform(transform);
174    executor.execute_one(request, agent_arguments).await
175}
176
177#[cfg(feature = "mcp")]
178impl crate::cli::command::CommandResponse for Response {
179    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
180        crate::cli::command::McpResponseItem::JSONL(serde_json::to_value(self).unwrap())
181    }
182}
183
184pub mod request_schema;
185
186pub mod response_schema;