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