Skip to main content

objectiveai_sdk/cli/command/db/
mod.rs

1//! `db` — direct database access at the CLI level.
2//!
3//! Leaves:
4//! - `query` — execute arbitrary single-statement read-only SQL
5//!   with a required timeout and an optional per-response token
6//!   budget.
7//! - `config` — the connection settings.
8//!
9//! The postgres vehicle itself has no spawn/kill commands: the daemon
10//! starts it as a leashed resident child on first need and it dies
11//! with the daemon (`daemon kill`).
12
13use crate::cli::command::CommandRequest;
14
15pub mod config;
16pub mod query;
17
18#[derive(clap::Subcommand)]
19pub enum Command {
20    Config {
21        #[command(subcommand)]
22        command: config::Command,
23    },
24    /// Execute an arbitrary single-statement read-only SQL query
25    /// against the configured postgres pool.
26    Query(query::Command),
27}
28
29#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
30#[serde(untagged)]
31#[schemars(rename = "cli.command.db.Request")]
32pub enum Request {
33    #[schemars(title = "Config")]
34    Config(config::Request),
35    #[schemars(title = "Query")]
36    Query(query::Request),
37    #[schemars(title = "QueryRequestSchema")]
38    QueryRequestSchema(query::request_schema::Request),
39    #[schemars(title = "QueryResponseSchema")]
40    QueryResponseSchema(query::response_schema::Request),
41}
42
43// Exempt from json-schema coverage: tier aggregate (see the root
44// `ResponseItem` in command.rs - TS7056).
45#[objectiveai_sdk_macros::json_schema_ignore]
46#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
47#[schemars(rename = "cli.command.db.ResponseItem")]
48#[serde(untagged)]
49pub enum ResponseItem {
50    #[schemars(title = "Config")]
51    Config(config::Response),
52    #[schemars(title = "Query")]
53    Query(query::Response),
54    #[schemars(title = "QueryRequestSchema")]
55    QueryRequestSchema(query::request_schema::Response),
56    #[schemars(title = "QueryResponseSchema")]
57    QueryResponseSchema(query::response_schema::Response),
58}
59
60#[cfg(feature = "mcp")]
61impl crate::cli::command::CommandResponse for ResponseItem {
62    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
63        match self {
64            ResponseItem::Config(v) => v.into_mcp(),
65            ResponseItem::Query(v) => v.into_mcp(),
66            ResponseItem::QueryRequestSchema(v) => v.into_mcp(),
67            ResponseItem::QueryResponseSchema(v) => v.into_mcp(),
68        }
69    }
70}
71
72impl TryFrom<Command> for Request {
73    type Error = crate::cli::command::FromArgsError;
74    fn try_from(command: Command) -> Result<Self, Self::Error> {
75        match command {
76            Command::Config { command } =>
77                Ok(Request::Config(config::Request::try_from(command)?)),
78            Command::Query(cmd) => match cmd.schema {
79                None => Ok(Request::Query(query::Request::try_from(cmd.args)?)),
80                Some(query::Schema::RequestSchema(args)) => Ok(
81                    Request::QueryRequestSchema(query::request_schema::Request::try_from(args)?),
82                ),
83                Some(query::Schema::ResponseSchema(args)) => Ok(
84                    Request::QueryResponseSchema(query::response_schema::Request::try_from(args)?),
85                ),
86            },
87        }
88    }
89}
90
91impl CommandRequest for Request {
92    fn request_base(&self) -> &crate::cli::command::RequestBase {
93        match self {
94            Request::Config(inner) => inner.request_base(),
95            Request::Query(inner) => inner.request_base(),
96            Request::QueryRequestSchema(inner) => inner.request_base(),
97            Request::QueryResponseSchema(inner) => inner.request_base(),
98        }
99    }
100
101    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
102        match self {
103            Request::Config(inner) => inner.request_base_mut(),
104            Request::Query(inner) => inner.request_base_mut(),
105            Request::QueryRequestSchema(inner) => inner.request_base_mut(),
106            Request::QueryResponseSchema(inner) => inner.request_base_mut(),
107        }
108    }
109}
110
111#[cfg(feature = "cli-executor")]
112pub async fn execute<E: crate::cli::command::CommandExecutor>(
113    executor: &E,
114    request: Request,
115    agent_arguments: Option<&crate::cli::command::AgentArguments>,
116) -> Result<
117    std::pin::Pin<Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>>,
118    E::Error,
119> {
120    use futures::StreamExt;
121    let stream: std::pin::Pin<
122        Box<dyn futures::Stream<Item = Result<ResponseItem, E::Error>> + Send>,
123    > = match request {
124            Request::Config(req) => {
125                let inner = config::execute(executor, req, agent_arguments).await?;
126                Box::pin(inner.map(|r| r.map(ResponseItem::Config)))
127            }
128        Request::Query(req) => {
129            let value = query::execute(executor, req, agent_arguments).await?;
130            Box::pin(crate::cli::command::StreamOnce::new(Ok(
131                ResponseItem::Query(value),
132            )))
133        }
134        Request::QueryRequestSchema(req) => {
135            let value = query::request_schema::execute(executor, req, agent_arguments).await?;
136            Box::pin(crate::cli::command::StreamOnce::new(Ok(
137                ResponseItem::QueryRequestSchema(value),
138            )))
139        }
140        Request::QueryResponseSchema(req) => {
141            let value = query::response_schema::execute(executor, req, agent_arguments).await?;
142            Box::pin(crate::cli::command::StreamOnce::new(Ok(
143                ResponseItem::QueryResponseSchema(value),
144            )))
145        }
146    };
147    Ok(stream)
148}
149
150#[cfg(feature = "cli-executor")]
151pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
152    executor: &E,
153    request: Request,
154    transform: crate::cli::command::Transform,
155    agent_arguments: Option<&crate::cli::command::AgentArguments>,
156) -> Result<
157    std::pin::Pin<Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>>,
158    E::Error,
159> {
160    let stream: std::pin::Pin<
161        Box<dyn futures::Stream<Item = Result<serde_json::Value, E::Error>> + Send>,
162    > = match request {
163            Request::Config(req) => {
164                let inner = config::execute_transform(executor, req, transform, agent_arguments).await?;
165                Box::pin(inner)
166            }
167        Request::Query(req) => {
168            let value = query::execute_transform(executor, req, transform, agent_arguments).await?;
169            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
170        }
171        Request::QueryRequestSchema(req) => {
172            let value =
173                query::request_schema::execute_transform(executor, req, transform, agent_arguments).await?;
174            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
175        }
176        Request::QueryResponseSchema(req) => {
177            let value =
178                query::response_schema::execute_transform(executor, req, transform, agent_arguments).await?;
179            Box::pin(crate::cli::command::StreamOnce::new(Ok(value)))
180        }
181    };
182    Ok(stream)
183}
184
185/// `/listen` mirror of [`Request`]: one variant per child, wrapping
186/// its `ListenerExecution`. See [`crate::cli::broadcast_listener`].
187#[cfg(feature = "cli-listener")]
188pub enum ListenerExecution {
189    Config(config::ListenerExecution),
190    Query(query::ListenerExecution),
191    QueryRequestSchema(query::request_schema::ListenerExecution),
192    QueryResponseSchema(query::response_schema::ListenerExecution),
193}