Skip to main content

objectiveai_sdk/cli/command/tools/run/
mod.rs

1//! `tools run` — async handler stub.
2
3use crate::cli::command::CommandRequest;
4
5#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
6#[schemars(rename = "cli.command.tools.run.Request")]
7pub struct Request {
8    pub path_type: Path,
9    pub owner: String,
10    pub name: String,
11    pub version: String,
12    pub args: Vec<String>,
13    #[serde(flatten)]
14    pub base: crate::cli::command::RequestBase,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
18#[schemars(rename = "cli.command.tools.run.Path")]
19pub enum Path {
20    #[serde(rename = "tools/run")]
21    ToolsRun,
22}
23
24impl CommandRequest for Request {
25    fn request_base(&self) -> &crate::cli::command::RequestBase {
26        &self.base
27    }
28
29    fn request_base_mut(&mut self) -> Option<&mut crate::cli::command::RequestBase> {
30        Some(&mut self.base)
31    }
32}
33
34#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
35#[serde(untagged)]
36#[schemars(rename = "cli.command.tools.run.ResponseItem")]
37pub enum ResponseItem {
38    #[schemars(title = "Stdout")]
39    Stdout(String),
40    #[schemars(title = "Stderr")]
41    Stderr(crate::cli::Error),
42}
43
44#[derive(clap::Args)]
45#[command(group(clap::ArgGroup::new("owner_required").required(true).args(["owner"])))]
46#[command(group(clap::ArgGroup::new("name_required").required(true).args(["name"])))]
47#[command(group(clap::ArgGroup::new("version_required").required(true).args(["version"])))]
48pub struct Args {
49    /// Tool owner (GitHub `<owner>` segment). Required.
50    #[arg(long)]
51    pub owner: Option<String>,
52    /// Tool name (repository segment). Required.
53    #[arg(long)]
54    pub name: Option<String>,
55    /// Tool version. Required.
56    #[arg(long)]
57    pub version: Option<String>,
58    /// Arguments appended to the tool's exec vector, as a JSON array
59    /// of strings (e.g. `--args '["--flag","value"]'`).
60    #[arg(long)]
61    pub args: Option<String>,
62    #[command(flatten)]
63    pub base: crate::cli::command::RequestBaseArgs,
64}
65
66#[derive(clap::Args)]
67#[command(args_conflicts_with_subcommands = true)]
68pub struct Command {
69    #[command(flatten)]
70    pub args: Args,
71    #[command(subcommand)]
72    pub schema: Option<Schema>,
73}
74
75#[derive(clap::Subcommand)]
76pub enum Schema {
77    /// Emit the JSON Schema for this leaf's `Request` type and exit.
78    RequestSchema(request_schema::Args),
79    /// Emit the JSON Schema for this leaf's `Response` type and exit.
80    ResponseSchema(response_schema::Args),
81}
82
83impl TryFrom<Args> for Request {
84    type Error = crate::cli::command::FromArgsError;
85    fn try_from(args: Args) -> Result<Self, Self::Error> {
86        let parsed_args: Vec<String> = match args.args {
87            Some(s) => {
88                let mut de = serde_json::Deserializer::from_str(&s);
89                serde_path_to_error::deserialize(&mut de).map_err(|source| {
90                    crate::cli::command::FromArgsError {
91                        field: "args",
92                        source: source.into(),
93                    }
94                })?
95            }
96            None => Vec::new(),
97        };
98        Ok(Self {
99            path_type: Path::ToolsRun,
100            owner: args.owner.ok_or_else(|| {
101                crate::cli::command::FromArgsError::path_parse(
102                    "owner",
103                    "--owner is required".to_string(),
104                )
105            })?,
106            name: args.name.ok_or_else(|| {
107                crate::cli::command::FromArgsError::path_parse(
108                    "name",
109                    "--name is required".to_string(),
110                )
111            })?,
112            version: args.version.ok_or_else(|| {
113                crate::cli::command::FromArgsError::path_parse(
114                    "version",
115                    "--version is required".to_string(),
116                )
117            })?,
118            args: parsed_args,
119            base: args.base.into(),
120        })
121    }
122}
123
124#[cfg(feature = "cli-executor")]
125pub async fn execute<E: crate::cli::command::CommandExecutor>(
126    executor: &E,
127    mut request: Request,
128
129        agent_arguments: Option<&crate::cli::command::AgentArguments>,
130    ) -> Result<E::Stream<ResponseItem>, E::Error> {
131    request.base.clear_transform();
132    executor.execute(request, agent_arguments).await
133}
134
135#[cfg(feature = "cli-executor")]
136pub async fn execute_transform<E: crate::cli::command::CommandExecutor>(
137    executor: &E,
138    mut request: Request,
139    transform: crate::cli::command::Transform,
140
141        agent_arguments: Option<&crate::cli::command::AgentArguments>,
142    ) -> Result<E::Stream<serde_json::Value>, E::Error> {
143    request.base.set_transform(transform);
144    executor.execute(request, agent_arguments).await
145}
146
147#[cfg(feature = "mcp")]
148impl crate::cli::command::CommandResponse for ResponseItem {
149    fn into_mcp(self) -> crate::cli::command::McpResponseItem {
150        use crate::agent::completions::message::RichContentPart;
151        use crate::cli::command::McpResponseItem;
152        match self {
153            ResponseItem::Stdout(s) => {
154                // Stdout line that happens to be a `data:<mime>;base64,...`
155                // URL gets upgraded to a typed media block; otherwise
156                // it rides through as a bare `Value::String`.
157                if let Some((mime, payload)) = crate::data_url::parse_data_url(&s) {
158                    let part = RichContentPart::from_blob(
159                        mime,
160                        payload.to_string(),
161                        None,
162                    );
163                    return McpResponseItem::Media(part.into());
164                }
165                McpResponseItem::JSONL(serde_json::Value::String(s))
166            }
167            ResponseItem::Stderr(e) => e.into_mcp(),
168        }
169    }
170}
171
172pub mod request_schema;
173
174
175pub mod response_schema;