diff --git a/cli/.gitignore b/cli/.gitignore new file mode 100644 index 00000000..6aa10640 --- /dev/null +++ b/cli/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 00000000..7414894f --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "apc" +version = "1.0.0" +authors = ["AI Engineer Foundation "] +description = "Agent Protocol CLI for communication with an agent." +license = "MIT" +edition = "2018" + +[dependencies] +serde = "^1.0" +serde_derive = "^1.0" +serde_with = "^2.0" +serde_json = "^1.0" +url = "^2.2" +uuid = { version = "^1.0", features = ["serde", "v4"] } +clap = { version = "4.4.6", features = ["derive"] } +tokio = { version = "1.33.0", features = ["full"] } +[dependencies.reqwest] +version = "^0.11" +features = ["json", "multipart"] diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 00000000..95382e3b --- /dev/null +++ b/cli/README.md @@ -0,0 +1,41 @@ +# Agent Protocol CLI + +## Introduction + +This is a cross-platform CLI tool for the agent protocol. + +## Execution + +Prerequisites: make sure `cargo` and `rust` are available. + +To execute the tool, run: + +```rust +cargo build +cargo run -h +``` + +## Formatting + +More about code formatting, see [rustfmt](https://github.com/rust-lang/rustfmt) + +```rust +rustup toolchain install nightly +rustup component add rustfmt --toolchain nightly +cargo +nightly fmt +``` + +Updates: let's just use the default rust format settings by running `cargo fmt` + +## The AP API + +Questions: +1. How to visualize the Spec? +1. Shall we modualize the Spec? + +## Generator Script + +The generator script has some issue. + +For instance, the `TaskRequestBody` has a field called `input` and it is optional. +However, after coversion, its type becomes `Option>` \ No newline at end of file diff --git a/cli/generate_from_spec.sh b/cli/generate_from_spec.sh new file mode 100755 index 00000000..d467442f --- /dev/null +++ b/cli/generate_from_spec.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +mkdir tmp +cd tmp + +openapi-generator-cli generate -i ../../schemas/openapi.yml -g rust + +mv ./src/apis ../src/ +mv ./src/models ../src/ +mv ./src/lib.rs ../src/ +mv ./docs ../docs + +cd ../ + +rm -rfv tmp diff --git a/cli/src/apis/agent_api.rs b/cli/src/apis/agent_api.rs new file mode 100644 index 00000000..7fbb42f8 --- /dev/null +++ b/cli/src/apis/agent_api.rs @@ -0,0 +1,500 @@ +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +use reqwest; + +use super::{configuration, Error}; +use crate::apis::ResponseContent; + +/// struct for typed errors of method [`create_agent_task`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CreateAgentTaskError { + Status422(serde_json::Value), + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`download_agent_task_artifact`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DownloadAgentTaskArtifactError { + Status404(crate::models::GetAgentTask404Response), + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`execute_agent_task_step`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ExecuteAgentTaskStepError { + Status404(crate::models::GetAgentTask404Response), + Status422(serde_json::Value), + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_agent_task`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetAgentTaskError { + Status404(crate::models::GetAgentTask404Response), + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_agent_task_step`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetAgentTaskStepError { + Status404(crate::models::GetAgentTask404Response), + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`list_agent_task_artifacts`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ListAgentTaskArtifactsError { + Status404(crate::models::GetAgentTask404Response), + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`list_agent_task_steps`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ListAgentTaskStepsError { + Status404(crate::models::GetAgentTask404Response), + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`list_agent_tasks`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ListAgentTasksError { + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`upload_agent_task_artifacts`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum UploadAgentTaskArtifactsError { + Status404(crate::models::GetAgentTask404Response), + DefaultResponse(), + UnknownValue(serde_json::Value), +} + +pub async fn create_agent_task( + configuration: &configuration::Configuration, + task_request_body: Option, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/ap/v1/agent/tasks", local_var_configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + local_var_req_builder = local_var_req_builder.json(&task_request_body); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn download_agent_task_artifact( + configuration: &configuration::Configuration, + task_id: &str, + artifact_id: &str, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/ap/v1/agent/tasks/{task_id}/artifacts/{artifact_id}", + local_var_configuration.base_path, + task_id = crate::apis::urlencode(task_id), + artifact_id = crate::apis::urlencode(artifact_id) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn execute_agent_task_step( + configuration: &configuration::Configuration, + task_id: &str, + step_request_body: Option, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/ap/v1/agent/tasks/{task_id}/steps", + local_var_configuration.base_path, + task_id = crate::apis::urlencode(task_id) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + local_var_req_builder = local_var_req_builder.json(&step_request_body); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn get_agent_task( + configuration: &configuration::Configuration, + task_id: &str, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/ap/v1/agent/tasks/{task_id}", + local_var_configuration.base_path, + task_id = crate::apis::urlencode(task_id) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn get_agent_task_step( + configuration: &configuration::Configuration, + task_id: &str, + step_id: &str, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/ap/v1/agent/tasks/{task_id}/steps/{step_id}", + local_var_configuration.base_path, + task_id = crate::apis::urlencode(task_id), + step_id = crate::apis::urlencode(step_id) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn list_agent_task_artifacts( + configuration: &configuration::Configuration, + task_id: &str, + current_page: Option, + page_size: Option, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/ap/v1/agent/tasks/{task_id}/artifacts", + local_var_configuration.base_path, + task_id = crate::apis::urlencode(task_id) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_str) = current_page { + local_var_req_builder = + local_var_req_builder.query(&[("current_page", &local_var_str.to_string())]); + } + if let Some(ref local_var_str) = page_size { + local_var_req_builder = + local_var_req_builder.query(&[("page_size", &local_var_str.to_string())]); + } + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn list_agent_task_steps( + configuration: &configuration::Configuration, + task_id: &str, + current_page: Option, + page_size: Option, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/ap/v1/agent/tasks/{task_id}/steps", + local_var_configuration.base_path, + task_id = crate::apis::urlencode(task_id) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_str) = current_page { + local_var_req_builder = + local_var_req_builder.query(&[("current_page", &local_var_str.to_string())]); + } + if let Some(ref local_var_str) = page_size { + local_var_req_builder = + local_var_req_builder.query(&[("page_size", &local_var_str.to_string())]); + } + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn list_agent_tasks( + configuration: &configuration::Configuration, + current_page: Option, + page_size: Option, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!("{}/ap/v1/agent/tasks", local_var_configuration.base_path); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::GET, local_var_uri_str.as_str()); + + if let Some(ref local_var_str) = current_page { + local_var_req_builder = + local_var_req_builder.query(&[("current_page", &local_var_str.to_string())]); + } + if let Some(ref local_var_str) = page_size { + local_var_req_builder = + local_var_req_builder.query(&[("page_size", &local_var_str.to_string())]); + } + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} + +pub async fn upload_agent_task_artifacts( + configuration: &configuration::Configuration, + task_id: &str, + _file: std::path::PathBuf, + relative_path: Option<&str>, +) -> Result> { + let local_var_configuration = configuration; + + let local_var_client = &local_var_configuration.client; + + let local_var_uri_str = format!( + "{}/ap/v1/agent/tasks/{task_id}/artifacts", + local_var_configuration.base_path, + task_id = crate::apis::urlencode(task_id) + ); + let mut local_var_req_builder = + local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); + + if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { + local_var_req_builder = + local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); + } + let mut local_var_form = reqwest::multipart::Form::new(); + // TODO: support file upload for 'file' parameter + if let Some(local_var_param_value) = relative_path { + local_var_form = local_var_form.text("relative_path", local_var_param_value.to_string()); + } + local_var_req_builder = local_var_req_builder.multipart(local_var_form); + + let local_var_req = local_var_req_builder.build()?; + let local_var_resp = local_var_client.execute(local_var_req).await?; + + let local_var_status = local_var_resp.status(); + let local_var_content = local_var_resp.text().await?; + + if !local_var_status.is_client_error() && !local_var_status.is_server_error() { + serde_json::from_str(&local_var_content).map_err(Error::from) + } else { + let local_var_entity: Option = + serde_json::from_str(&local_var_content).ok(); + let local_var_error = ResponseContent { + status: local_var_status, + content: local_var_content, + entity: local_var_entity, + }; + Err(Error::ResponseError(local_var_error)) + } +} diff --git a/cli/src/apis/configuration.rs b/cli/src/apis/configuration.rs new file mode 100644 index 00000000..36faf74e --- /dev/null +++ b/cli/src/apis/configuration.rs @@ -0,0 +1,34 @@ +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + * + * Modified by: jzanecook + */ + +#[derive(Debug, Clone)] +pub struct Configuration { + pub base_path: String, + pub user_agent: Option, + pub client: reqwest::Client, +} + +impl Configuration { + pub fn new() -> Configuration { + Configuration::default() + } +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + base_path: "http://0.0.0.0:8000".to_owned(), + user_agent: Some("Agent-CLI/v1".to_owned()), + client: reqwest::Client::new(), + } + } +} diff --git a/cli/src/apis/mod.rs b/cli/src/apis/mod.rs new file mode 100644 index 00000000..60de40ba --- /dev/null +++ b/cli/src/apis/mod.rs @@ -0,0 +1,96 @@ +use std::{error, fmt}; + +#[derive(Debug, Clone)] +pub struct ResponseContent { + pub status: reqwest::StatusCode, + pub content: String, + pub entity: Option, +} + +#[derive(Debug)] +pub enum Error { + Reqwest(reqwest::Error), + Serde(serde_json::Error), + Io(std::io::Error), + ResponseError(ResponseContent), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (module, e) = match self { + Error::Reqwest(e) => ("reqwest", e.to_string()), + Error::Serde(e) => ("serde", e.to_string()), + Error::Io(e) => ("IO", e.to_string()), + Error::ResponseError(e) => ("response", format!("status code {}", e.status)), + }; + write!(f, "error in {}: {}", module, e) + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(match self { + Error::Reqwest(e) => e, + Error::Serde(e) => e, + Error::Io(e) => e, + Error::ResponseError(_) => return None, + }) + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Reqwest(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Serde(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::Io(e) + } +} + +pub fn urlencode>(s: T) -> String { + ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() +} + +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + } + serde_json::Value::String(s) => { + params.push((format!("{}[{}]", prefix, key), s.clone())) + } + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + +pub mod agent_api; + +pub mod configuration; diff --git a/cli/src/args.rs b/cli/src/args.rs new file mode 100644 index 00000000..663765f2 --- /dev/null +++ b/cli/src/args.rs @@ -0,0 +1,186 @@ +use clap::{Args, Parser, Subcommand}; + +/// Program to call agents +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct APCli { + /// URL of the Agent + #[arg(short, long, default_value_t = String::from("http://localhost:8000"))] + pub url: String, + + /// Command to run + #[command(subcommand)] + pub commands: Commands, +} + +#[derive(Subcommand, Debug)] +pub enum Commands { + #[command(name = "task")] + Task(TaskArgs), +} + +#[derive(Args, Debug)] +pub struct TaskArgs { + /// Task ID + #[arg(name = "id")] + pub id: Option, + + #[command(subcommand)] + pub commands: TaskCommands, +} + +#[derive(Subcommand, Debug)] +pub enum TaskCommands { + /// List Tasks + #[command(name = "list")] + List(TaskListArgs), + + /// Create a New Task + #[command(name = "create")] + Create(TaskCreateArgs), + + /// Execute or List Steps for a Task + /// Example: apc task step list + #[command(name = "step")] + Step(TaskStepArgs), + + /// Upload or Download Artifacts + #[command(name = "artifact")] + Artifact(TaskArtifactArgs), +} + +#[derive(Args, Debug)] +pub struct TaskListArgs { + /// Page Number + /// Default: 1 + #[arg(short, long, default_value_t = 1)] + pub page: i32, + + /// Page Size + /// Default: 10 + #[arg(short = 's', long, default_value_t = 10)] + pub page_size: i32, +} + +#[derive(Args, Debug)] +pub struct TaskCreateArgs { + /// Task Input + /// Example: --input 'Write Washington to a file' + #[arg(name = "input", short = 'i', long)] + pub input: String, + + /// Additional Input + /// Default: None + /// Example: --additional-input '{"foo": "bar"}' + #[arg(name = "additional_input", short = 'a', long)] + pub additional_input: Option, +} + +#[derive(Args, Debug)] +pub struct TaskStepArgs { + /// Step ID + #[arg(name = "id")] + pub id: Option, + + #[command(subcommand)] + pub commands: TaskStepCommands, +} + +#[derive(Subcommand, Debug)] +pub enum TaskStepCommands { + /// List Steps for this Task + #[command(name = "list")] + List(TaskStepListArgs), + + /// Execute a New Step for thiagent-protocols Task + #[command(name = "execute")] + Execute(TaskStepExecuteArgs), +} + +#[derive(Args, Debug)] +pub struct TaskStepListArgs { + /// Page Number + /// Default: 1 + #[arg(short, long, default_value_t = 1)] + pub page: u8, + + /// Page Size + /// Default: 10 + #[arg(short = 's', long, default_value_t = 10)] + pub page_size: u8, +} + +#[derive(Args, Debug)] +pub struct TaskStepExecuteArgs { + /// Step Input + /// Example: --input 'Write Washington to a file' + #[arg(name = "input", short = 'i', long)] + pub input: String, + + /// Additional Input + /// Default: None + /// Example: --additional-input '{"foo": "bar"}' + #[arg(name = "additional_input", short = 'a', long)] + pub additional_input: Option, +} + +#[derive(Args, Debug)] +pub struct TaskArtifactArgs { + /// Artifact ID + #[arg(name = "id")] + pub id: Option, + + #[command(subcommand)] + pub commands: TaskArtifactCommands, +} + +#[derive(Subcommand, Debug)] +pub enum TaskArtifactCommands { + /// List Artifacts for this Task + #[command(name = "list")] + List(TaskArtifactListArgs), + + /// Upload a New Artifact for this Task + #[command(name = "upload")] + Upload(TaskArtifactUploadArgs), + + /// Download an Artifact for this Task + #[command(name = "download")] + Download(TaskArtifactDownloadArgs), +} + +#[derive(Args, Debug)] +pub struct TaskArtifactListArgs { + /// Page Number + /// Default: 1 + #[arg(short, long, default_value_t = 1)] + pub page: u8, + + /// Page Size + /// Default: 10 + #[arg(short = 's', long, default_value_t = 10)] + pub page_size: u8, +} + +#[derive(Args, Debug)] +pub struct TaskArtifactUploadArgs { + /// Artifact Input + /// Example: --input 'Write Washington to a file' + #[arg(name = "input", short = 'i', long)] + pub input: String, + + /// Additional Input + /// Default: None + /// Example: --additional-input '{"foo": "bar"}' + #[arg(name = "additional_input", short = 'a', long)] + pub additional_input: Option, +} + +#[derive(Args, Debug)] +pub struct TaskArtifactDownloadArgs { + /// Output File + /// Default: None + /// Example: --output-file 'washington.txt' + #[arg(name = "output_file", short = 'o', long)] + pub output_file: Option, +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs new file mode 100644 index 00000000..fc22e4e4 --- /dev/null +++ b/cli/src/lib.rs @@ -0,0 +1,10 @@ +#[macro_use] +extern crate serde_derive; + +extern crate reqwest; +extern crate serde; +extern crate serde_json; +extern crate url; + +pub mod apis; +pub mod models; diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 00000000..84d4c01e --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,153 @@ +use apc::apis; +use clap::Parser; +use tokio; +// use apc::models; + +mod args; +use args::{APCli, Commands, TaskArtifactCommands, TaskCommands, TaskStepCommands}; + +#[tokio::main] +async fn main() { + let args = APCli::parse(); + + let configuration: apis::configuration::Configuration = apis::configuration::Configuration { + base_path: args.url.clone(), + user_agent: Some("Agent-CLI/v1".to_owned()), + client: reqwest::Client::new(), + }; + + println!("{:?}", args); + + match args.commands { + Commands::Task(task_args) => { + match task_args.commands { + TaskCommands::List(task_list_args) => { + if let Some(_) = task_args.id { + eprintln!("Error: Cannot list tasks and use an ID simultaneously. Please choose one operation."); + std::process::exit(1); + } + let tasks = apis::agent_api::list_agent_tasks( + &configuration, + Some(task_list_args.page), + Some(task_list_args.page_size), + ) + .await + .unwrap(); + + println!("{:?}", tasks); + + println!("Will be listing tasks with arguments: {:?}", task_list_args) + } + TaskCommands::Create(task_create_args) => { + if let Some(_) = task_args.id { + eprintln!("Error: Cannot create a task and use an ID simultaneously. Please choose one operation."); + std::process::exit(1); + } + // let task = apis::agent_api::create_agent_task( + // &args.url, + // task_create_args.input, + // task_create_args.additional_input, + // ) + // .unwrap(); + + // println!("{:?}", task); + println!("Will be creating task with input: {:?}", task_create_args); + } + TaskCommands::Step(task_step_args) => { + // ! TODO: This is a hacky way to do this. There has to be a better way. + if let Some(id) = task_args.id { + match task_step_args.commands { + TaskStepCommands::List(task_step_list_args) => { + // let steps = apis::agent_api::list_agent_task_steps( + // &args.url, + // &id, + // task_step_list_args.page, + // task_step_list_args.page_size, + // ) + // .unwrap(); + + // println!("{:?}", steps); + println!( + "Will be listing steps for task {} with arguments: {:?}", + id, task_step_list_args + ); + } + TaskStepCommands::Execute(task_step_execute_args) => { + // let step = apis::agent_api::execute_agent_task_step( + // &args.url, + // &id, + // task_step_execute_args.input, + // task_step_execute_args.additional_input, + // ) + // .unwrap(); + + // println!("{:?}", step); + println!( + "Will be executing step for task {} with input: {:?}", + id, task_step_execute_args + ); + } + } + } else { + eprintln!("Error: Cannot execute a step without a task ID. Please specify a task ID."); + std::process::exit(1); + } + } + TaskCommands::Artifact(task_artifact_args) => { + // ! TODO: Same as the other, this is a hacky way to do this. + if let Some(id) = task_args.id { + match task_artifact_args.commands { + TaskArtifactCommands::List(task_artifact_list_args) => { + // let artifacts = apis::agent_api::list_agent_task_artifacts( + // &args.url, + // &id, + // task_artifact_list_args.page, + // task_artifact_list_args.page_size, + // ) + // .unwrap(); + + // println!("{:?}", artifacts); + println!( + "Will be listing artifacts for task {} with arguments: {:?}", + id, task_artifact_list_args + ); + } + TaskArtifactCommands::Upload(task_artifact_upload_args) => { + // let artifact = apis::agent_api::upload_agent_task_artifact( + // &args.url, + // &id, + // task_artifact_upload_args.input, + // task_artifact_upload_args.additional_input, + // ) + // .unwrap(); + + // println!("{:?}", artifact); + println!( + "Will be uploading artifact for task {} with input: {:?}", + id, task_artifact_upload_args + ); + } + TaskArtifactCommands::Download(task_artifact_download_args) => { + // let artifact = apis::agent_api::download_agent_task_artifact( + // &args.url, + // &id, + // task_artifact_download_args.output_file, + // ) + // .unwrap(); + + // println!("{:?}", artifact); + println!( + "Will be downloading artifact for task {} with arguments: {:?}", + id, task_artifact_download_args + ); + } + } + } else { + eprintln!("Error: Cannot upload or download an artifact without a task ID. Please specify a task ID."); + std::process::exit(1) + } + } + } + } + } +} diff --git a/cli/src/models/artifact.rs b/cli/src/models/artifact.rs new file mode 100644 index 00000000..2e8f5139 --- /dev/null +++ b/cli/src/models/artifact.rs @@ -0,0 +1,46 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +/// Artifact : An Artifact either created by or submitted to the agent. + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Artifact { + /// ID of the artifact. + #[serde(rename = "artifact_id")] + pub artifact_id: String, + /// Whether the artifact has been created by the agent. + #[serde(rename = "agent_created")] + pub agent_created: bool, + /// Filename of the artifact. + #[serde(rename = "file_name")] + pub file_name: String, + /// Relative path of the artifact in the agent's workspace. + #[serde( + rename = "relative_path", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub relative_path: Option>, +} + +impl Artifact { + /// An Artifact either created by or submitted to the agent. + pub fn new(artifact_id: String, agent_created: bool, file_name: String) -> Artifact { + Artifact { + artifact_id, + agent_created, + file_name, + relative_path: None, + } + } +} diff --git a/cli/src/models/get_agent_task_404_response.rs b/cli/src/models/get_agent_task_404_response.rs new file mode 100644 index 00000000..68dc31ea --- /dev/null +++ b/cli/src/models/get_agent_task_404_response.rs @@ -0,0 +1,24 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GetAgentTask404Response { + /// Message stating the entity was not found + #[serde(rename = "message")] + pub message: String, +} + +impl GetAgentTask404Response { + pub fn new(message: String) -> GetAgentTask404Response { + GetAgentTask404Response { message } + } +} diff --git a/cli/src/models/mod.rs b/cli/src/models/mod.rs new file mode 100644 index 00000000..c251ef86 --- /dev/null +++ b/cli/src/models/mod.rs @@ -0,0 +1,20 @@ +pub mod artifact; +pub use self::artifact::Artifact; +pub mod get_agent_task_404_response; +pub use self::get_agent_task_404_response::GetAgentTask404Response; +pub mod pagination; +pub use self::pagination::Pagination; +pub mod step; +pub use self::step::Step; +pub mod step_request_body; +pub use self::step_request_body::StepRequestBody; +pub mod task; +pub use self::task::Task; +pub mod task_artifacts_list_response; +pub use self::task_artifacts_list_response::TaskArtifactsListResponse; +pub mod task_list_response; +pub use self::task_list_response::TaskListResponse; +pub mod task_request_body; +pub use self::task_request_body::TaskRequestBody; +pub mod task_steps_list_response; +pub use self::task_steps_list_response::TaskStepsListResponse; diff --git a/cli/src/models/pagination.rs b/cli/src/models/pagination.rs new file mode 100644 index 00000000..97d883a7 --- /dev/null +++ b/cli/src/models/pagination.rs @@ -0,0 +1,43 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Pagination { + /// Total number of items. + #[serde(rename = "total_items")] + pub total_items: i32, + /// Total number of pages. + #[serde(rename = "total_pages")] + pub total_pages: i32, + /// Current_page page number. + #[serde(rename = "current_page")] + pub current_page: i32, + /// Number of items per page. + #[serde(rename = "page_size")] + pub page_size: i32, +} + +impl Pagination { + pub fn new( + total_items: i32, + total_pages: i32, + current_page: i32, + page_size: i32, + ) -> Pagination { + Pagination { + total_items, + total_pages, + current_page, + page_size, + } + } +} diff --git a/cli/src/models/step.rs b/cli/src/models/step.rs new file mode 100644 index 00000000..1f042f8a --- /dev/null +++ b/cli/src/models/step.rs @@ -0,0 +1,105 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Step { + /// Input prompt for the step. + #[serde( + rename = "input", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub input: Option>, + /// Input parameters for the task step. Any value is allowed. + #[serde(rename = "additional_input", skip_serializing_if = "Option::is_none")] + pub additional_input: Option, + /// The ID of the task this step belongs to. + #[serde(rename = "task_id")] + pub task_id: String, + /// The ID of the task step. + #[serde(rename = "step_id")] + pub step_id: String, + /// The name of the task step. + #[serde( + rename = "name", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub name: Option>, + /// The status of the task step. + #[serde(rename = "status")] + pub status: Status, + /// Output of the task step. + #[serde( + rename = "output", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub output: Option>, + /// Output that the task step has produced. Any value is allowed. + #[serde( + rename = "additional_output", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub additional_output: Option>, + /// A list of artifacts that the step has produced. + #[serde(rename = "artifacts")] + pub artifacts: Vec, + /// Whether this is the last step in the task. + #[serde(rename = "is_last")] + pub is_last: bool, +} + +impl Step { + pub fn new( + task_id: String, + step_id: String, + status: Status, + artifacts: Vec, + is_last: bool, + ) -> Step { + Step { + input: None, + additional_input: None, + task_id, + step_id, + name: None, + status, + output: None, + additional_output: None, + artifacts, + is_last, + } + } +} + +/// The status of the task step. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum Status { + #[serde(rename = "created")] + Created, + #[serde(rename = "running")] + Running, + #[serde(rename = "completed")] + Completed, +} + +impl Default for Status { + fn default() -> Status { + Self::Created + } +} diff --git a/cli/src/models/step_request_body.rs b/cli/src/models/step_request_body.rs new file mode 100644 index 00000000..34ced793 --- /dev/null +++ b/cli/src/models/step_request_body.rs @@ -0,0 +1,38 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +/// StepRequestBody : Body of the task request. + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StepRequestBody { + /// Input prompt for the step. + #[serde( + rename = "input", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub input: Option>, + /// Input parameters for the task step. Any value is allowed. + #[serde(rename = "additional_input", skip_serializing_if = "Option::is_none")] + pub additional_input: Option, +} + +impl StepRequestBody { + /// Body of the task request. + pub fn new() -> StepRequestBody { + StepRequestBody { + input: None, + additional_input: None, + } + } +} diff --git a/cli/src/models/task.rs b/cli/src/models/task.rs new file mode 100644 index 00000000..b4818618 --- /dev/null +++ b/cli/src/models/task.rs @@ -0,0 +1,43 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Task { + /// Input prompt for the task. + #[serde( + rename = "input", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub input: Option>, + /// Input parameters for the task. Any value is allowed. + #[serde(rename = "additional_input", skip_serializing_if = "Option::is_none")] + pub additional_input: Option, + /// The ID of the task. + #[serde(rename = "task_id")] + pub task_id: String, + /// A list of artifacts that the task has produced. + #[serde(rename = "artifacts")] + pub artifacts: Vec, +} + +impl Task { + pub fn new(task_id: String, artifacts: Vec) -> Task { + Task { + input: None, + additional_input: None, + task_id, + artifacts, + } + } +} diff --git a/cli/src/models/task_artifacts_list_response.rs b/cli/src/models/task_artifacts_list_response.rs new file mode 100644 index 00000000..036db160 --- /dev/null +++ b/cli/src/models/task_artifacts_list_response.rs @@ -0,0 +1,31 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct TaskArtifactsListResponse { + #[serde(rename = "artifacts")] + pub artifacts: Vec, + #[serde(rename = "pagination")] + pub pagination: Box, +} + +impl TaskArtifactsListResponse { + pub fn new( + artifacts: Vec, + pagination: crate::models::Pagination, + ) -> TaskArtifactsListResponse { + TaskArtifactsListResponse { + artifacts, + pagination: Box::new(pagination), + } + } +} diff --git a/cli/src/models/task_list_response.rs b/cli/src/models/task_list_response.rs new file mode 100644 index 00000000..3dac6558 --- /dev/null +++ b/cli/src/models/task_list_response.rs @@ -0,0 +1,31 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct TaskListResponse { + #[serde(rename = "tasks")] + pub tasks: Vec, + #[serde(rename = "pagination")] + pub pagination: Box, +} + +impl TaskListResponse { + pub fn new( + tasks: Vec, + pagination: crate::models::Pagination, + ) -> TaskListResponse { + TaskListResponse { + tasks, + pagination: Box::new(pagination), + } + } +} diff --git a/cli/src/models/task_request_body.rs b/cli/src/models/task_request_body.rs new file mode 100644 index 00000000..3f1834ea --- /dev/null +++ b/cli/src/models/task_request_body.rs @@ -0,0 +1,38 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +/// TaskRequestBody : Body of the task request. + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct TaskRequestBody { + /// Input prompt for the task. + #[serde( + rename = "input", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub input: Option, + /// Input parameters for the task. Any value is allowed. + #[serde(rename = "additional_input", skip_serializing_if = "Option::is_none")] + pub additional_input: Option, +} + +impl TaskRequestBody { + /// Body of the task request. + pub fn new() -> TaskRequestBody { + TaskRequestBody { + input: None, + additional_input: None, + } + } +} diff --git a/cli/src/models/task_steps_list_response.rs b/cli/src/models/task_steps_list_response.rs new file mode 100644 index 00000000..a1124634 --- /dev/null +++ b/cli/src/models/task_steps_list_response.rs @@ -0,0 +1,31 @@ +use serde_derive::{Serialize, Deserialize}; + +/* + * Agent Protocol + * + * Specification of the API protocol for communication with an agent. + * + * The version of the OpenAPI document: v1 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct TaskStepsListResponse { + #[serde(rename = "steps")] + pub steps: Vec, + #[serde(rename = "pagination")] + pub pagination: Box, +} + +impl TaskStepsListResponse { + pub fn new( + steps: Vec, + pagination: crate::models::Pagination, + ) -> TaskStepsListResponse { + TaskStepsListResponse { + steps, + pagination: Box::new(pagination), + } + } +}