Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add base agent #12

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ Cargo.lock

# IDE files
.idea/
.vscode/
.vscode/
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
log = "0.4.0"
env_logger = "0.8.4"
anyhow = "1.0.62"
thiserror = "1.0.32"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"
clap = { version="4.1.6", features=["derive"] }
17 changes: 17 additions & 0 deletions agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,20 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
log = "0.4.0"
env_logger = "0.10.0"
anyhow = "1.0.69"
serialport = "4.2.0"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.93"
serde_yaml = "0.9"
clap = { version="4.1.6", features=["derive"] }
unshare = "0.7.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this crate used?

thiserror = "1.0.32"

[lib]
name = "agent_lib"
path = "lib/src/lib.rs"

[dev-dependencies]
rand = "0.8.5"
71 changes: 71 additions & 0 deletions agent/lib/src/api/comms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// This message is sent to the API server to indicate wether
// the agent is ready or not to receive messages.

pub const MESSAGE_SIZE_NB_BYTES: usize = 8;

/// Represents a message sent by the agent
pub struct Message {
/// These are characters e.g. 00002048
pub message_size: [u8; MESSAGE_SIZE_NB_BYTES],
/// stringified json, vec because size is unknown
pub message: Vec<u8>,
}

impl Message {
pub fn new(message_to_send: String) -> Self {
let mut message_size = [0; MESSAGE_SIZE_NB_BYTES];
let message = message_to_send.as_bytes().to_vec();

let string_size = format!("{:0>8}", message.len());
//We can't call directly as bytes as both &str and String sizes are not known at
//compile time unlike message_size

for (i, c) in string_size.chars().enumerate() {
message_size[i] = c as u8;
}

Self {
message_size,
message,
}
}

pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.message_size);
bytes.extend_from_slice(&self.message);
bytes
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn new_message_well_encoded() {
let message_data = "Hello world".to_string();
let message = Message::new(message_data);
assert_eq!(
message.message,
[72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
);
assert_eq!(message.message_size, [48, 48, 48, 48, 48, 48, 49, 49]);

assert_eq!(
message.to_bytes(),
[48, 48, 48, 48, 48, 48, 49, 49, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
);
}

#[test]
fn message_size_badly_encoded() {
let message_data = "Hello world".to_string();
let message = Message::new(message_data);
assert_eq!(
message.message,
[72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
);
assert_ne!(message.message_size, [48, 48, 48, 48, 48, 48, 49, 50]); // should be 11, is 12
}
}
3 changes: 3 additions & 0 deletions agent/lib/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod comms;
pub mod model;
pub mod service;
187 changes: 187 additions & 0 deletions agent/lib/src/api/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use serde::{Deserialize, Serialize};

/// Represents a file to be included in the workspace
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
pub struct FileModel {
/// Name of the file, paths relative to the workspace
pub filename: String,
/// Content of the file
pub content: String,
}

/// Identifies the type of the message
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
pub enum Type {
/// Status message to indicate that the agent is ready
#[serde(rename = "status")]
Status,
/// Request message
#[serde(rename = "request")]
Request,
/// Response message answering to a request message
#[serde(rename = "response")]
Response,
}

/// Code to tell what the Request/Response message is about
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
pub enum Code {
/// Represents a request to run the code or a response to such request
#[serde(rename = "run")]
Run,
/// Agent is ready to communicate
#[serde(rename = "ready")]
Ready,
}

/// Represents a Status message
#[derive(Deserialize, Serialize, Debug)]
pub struct StatusMessage {
/// Type of the message
pub r#type: Type,
/// Code of the message
pub code: Code,
}

impl StatusMessage {
pub fn new(code: Code) -> StatusMessage {
StatusMessage {
// r#type is a reserved keyword in Rust, so we need to use the raw identifier syntax
r#type: Type::Status,
code,
}
}
}

impl Default for StatusMessage {
fn default() -> Self {
Self::new(Code::Ready)
}
}

/// Serializes an Option<String> as a String by returning an empty string if the Option is None
fn serialize_optionnal_string<S>(value: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
fn serialize_optionnal_string<S>(value: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
fn serialize_optional_string<S>(value: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be fixed.

where
S: serde::Serializer,
{
match value {
Some(v) => serializer.serialize_str(v),
None => serializer.serialize_str(""),
}
}

/// Represents the output of a step
#[derive(Deserialize, Serialize, Debug)]
pub struct ResponseStep {
/// Command that was run
pub command: String,
/// Exit code of the command
#[serde(alias = "exitCode")]
pub exit_code: i32,
/// Stdout of the command. If it is None, it will be serialized as an empty string
/// to avoid api crashes
#[serde(serialize_with = "serialize_optionnal_string")]
pub stdout: Option<String>,
/// Stderr of the command
pub stderr: String,
}

impl ResponseStep {
pub fn new(
command: String,
exit_code: i32,
stdout: Option<String>,
stderr: String,
) -> ResponseStep {
ResponseStep {
command,
exit_code,
stdout,
stderr,
}
}
}

/// Contains the id of the request and the result of all steps
#[derive(Deserialize, Serialize, Debug)]
pub struct ResponseData {
/// Id of the request (UUID)
pub id: String,
/// Result of all steps
pub steps: Vec<ResponseStep>,
}

impl ResponseData {
pub fn new(id: String, steps: Vec<ResponseStep>) -> ResponseData {
ResponseData { id, steps }
}
}

/// Represents a Response message with code Type::Run, meaning that it is a response to a run code request
#[derive(Deserialize, Serialize, Debug)]
pub struct ResponseMessage {
/// Type of the message
pub r#type: Type,
/// Code of the message
pub code: Code,
/// Data of the message
pub data: ResponseData,
}

impl ResponseMessage {
pub fn new(data: ResponseData) -> ResponseMessage {
ResponseMessage {
r#type: Type::Response,
code: Code::Run,
data,
}
}
}

/// Represent a step in the request with type Type::Run
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub struct RequestStep {
/// Command to run
pub command: String,
/// Whether the stdout should be returned or not (stderr will alaways be)
#[serde(alias = "enableOutput")]
pub enable_output: bool,
}

/// Represents the data of a request message with type Type::Run
#[derive(Deserialize, Serialize, Debug)]
pub struct RequestData {
/// Id of the request (UUID)
pub id: String,
/// Files to be included in the workspace, paths relative to the workspace
pub files: Vec<FileModel>,
/// Steps to be executed
pub steps: Vec<RequestStep>,
}

impl RequestData {
pub fn new(id: String, files: Vec<FileModel>, steps: Vec<RequestStep>) -> RequestData {
RequestData { id, files, steps }
}
}

/// Represents a Request message with type Type::Run
#[derive(Deserialize, Serialize, Debug)]
pub struct RequestMessage {
/// Type of the message
pub r#type: Type,
/// Code of the message
pub code: Code,
/// Data of the message
pub data: RequestData,
}

impl RequestMessage {
pub fn new(data: RequestData) -> RequestMessage {
RequestMessage {
r#type: Type::Request,
code: Code::Run,
data,
}
}
}
Loading