From f306396383001c0ab043cd3b9840e007055bfed8 Mon Sep 17 00:00:00 2001 From: Jessica Black Date: Sat, 22 Feb 2025 23:18:04 -0800 Subject: [PATCH 1/3] Remove extra gitignores --- anna/.gitignore | 1 - winlock/.gitignore | 1 - 2 files changed, 2 deletions(-) delete mode 100644 anna/.gitignore delete mode 100644 winlock/.gitignore diff --git a/anna/.gitignore b/anna/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/anna/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/winlock/.gitignore b/winlock/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/winlock/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target From e1cad867e942808bb72a09c479e666a097bfcfbc Mon Sep 17 00:00:00 2001 From: Jessica Black Date: Sun, 23 Feb 2025 00:35:16 -0800 Subject: [PATCH 2/3] Add some basic scaffolding types --- anna/Cargo.toml | 2 +- anna/src/main.rs | 3 - winlock/Cargo.toml | 5 +- winlock/src/lib.rs | 156 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 142 insertions(+), 24 deletions(-) diff --git a/anna/Cargo.toml b/anna/Cargo.toml index fee3a0f..2d47200 100644 --- a/anna/Cargo.toml +++ b/anna/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "anna" version = "0.1.0" -edition = "2021" +edition = "2024" authors = ["Jessica Black "] license = "MPL-2.0" public = false diff --git a/anna/src/main.rs b/anna/src/main.rs index b6cdb42..b105e53 100644 --- a/anna/src/main.rs +++ b/anna/src/main.rs @@ -3,12 +3,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use color_eyre::Result; -use winlock::Agent; fn main() -> Result<()> { color_eyre::install()?; - let _agent = Agent::new(); - Ok(()) } diff --git a/winlock/Cargo.toml b/winlock/Cargo.toml index 33dea54..99ce8b3 100644 --- a/winlock/Cargo.toml +++ b/winlock/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "winlock" version = "0.1.0" -edition = "2021" +edition = "2024" authors = ["Jessica Black "] license = "MPL-2.0" public = false [dependencies] +bon = "3.3.2" +derive_more = { version = "2.0.1", features = ["full"] } +url = "2.5.4" diff --git a/winlock/src/lib.rs b/winlock/src/lib.rs index 36ec913..b1f78d0 100644 --- a/winlock/src/lib.rs +++ b/winlock/src/lib.rs @@ -8,36 +8,154 @@ //! Observatory in the late 1800s. She was known for her mathematical and //! computational work in astronomy. -/// The main agent type that coordinates interactions with the LLM. -#[derive(Debug)] +use std::{fmt::Write, path::PathBuf}; + +use bon::Builder; +use derive_more::{Debug, Display, Error, From}; +use url::Url; + +/// Errors returned by the agent when prompting. +#[derive(Debug, Display, Error)] +#[non_exhaustive] +pub enum Error {} + +/// The main agent type that coordinates interactions with a given codebase. +#[derive(Debug, Clone, Builder)] +#[non_exhaustive] pub struct Agent { - // Fields will be added as we implement functionality + /// The codebase with which the agent interacts. + #[builder(into)] + pub codebase: Codebase, + + /// The runner that the agent uses to execute code. + #[builder(into)] + pub runner: Runner, + + /// The LLM backend that the agent uses. + #[builder(into)] + pub backend: Backend, + + /// The system prompt used by the agent for each query. + #[builder(into)] + pub system: Context, } impl Agent { - /// Creates a new agent instance. - pub fn new() -> Self { - Self {} + /// Prompt the agent with the provided context and request. + pub fn prompt(&self, _rag: Vec, _prompt: String) -> Result { + todo!() + } +} + +/// Context provided to the LLM backend with a query. +#[derive(Clone, From)] +#[non_exhaustive] +pub struct Context(String); + +impl Context { + /// The inner context value. + pub fn inner(&self) -> &str { + &self.0 + } +} + +impl std::fmt::Display for Context { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if f.alternate() || self.0.len() <= 20 { + write!(f, "{}", self.0) + } else { + write_truncated(f, 20, &self.0) + } + } +} + +impl std::fmt::Debug for Context { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Context({self})") } } -pub fn add(left: u64, right: u64) -> u64 { - left + right +/// Describes a codebase location. +#[derive(Debug, Clone, From)] +#[non_exhaustive] +pub enum Codebase { + /// The root directory of the codebase on the local disk. + Path(PathBuf), +} + +/// Agents run arbitrary commands; these are of course potentially dangerous +/// to run on the main system (especially the main system of a dev machine). +/// +/// Runners provide a safe arena for executing code. +/// These are implemented using Docker; each [`Agent`] gets a [`Runner`] +/// with the [`Codebase`] bound to a volume inside the runner's context. +/// +/// This allows the agent to execute arbitrary commands that can modify +/// the [`Codebase`] without danger of those commands affecting the +/// rest of the system. +/// +/// A runner instance is specifically a reference to an +/// _already running_ executor inside an _already running_ +/// Docker container. +#[derive(Debug, Clone, Builder)] +#[non_exhaustive] +pub struct Runner { + /// Runners are actually programs inside a Docker container; + /// agents communicate with them using a locally bound address. + #[builder(into)] + pub address: Url, + + /// The identifier of the container in which the runner is running. + /// This is used for debugging containers in the Docker runtime + /// if the runner isn't responding to its address. + #[builder(into)] + pub container_id: String, +} + +/// Agents need an LLM backend to do much of anything. +/// Describes an LLM backend for an agent. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum Backend { + /// References the OpenAI API. + OpenAI(Url, ApiKey), } -#[cfg(test)] -mod tests { - use super::*; +/// Special type for API keys guarding them from being accidentally logged. +#[derive(Debug, Clone, From)] +#[debug("ApiKey(..)")] +#[non_exhaustive] +pub struct ApiKey(String); - #[test] - fn test_agent_creation() { - let agent = Agent::new(); - assert!(std::mem::size_of_val(&agent) > 0); +impl ApiKey { + /// The inner key value. + pub fn inner(&self) -> &str { + &self.0 + } +} + +/// Drops the middle of a string to fit within a given length. +fn write_truncated(f: &mut std::fmt::Formatter<'_>, len: usize, content: &str) -> std::fmt::Result { + if content.len() <= len { + return write!(f, "{content}"); } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + if len <= 2 { + for _ in 0..len { + f.write_char('.')?; + } + return Ok(()); } + + let half = len / 2; + let mut chars = content.chars(); + for c in chars.by_ref().take(half) { + f.write_char(c)?; + } + f.write_str("..")?; + for c in chars.rev().take(half) { + f.write_char(c)?; + } + + Ok(()) } From 9e514255e7c2153733741e9aa705b866c8eba17f Mon Sep 17 00:00:00 2001 From: Jessica Black Date: Sun, 23 Feb 2025 00:44:06 -0800 Subject: [PATCH 3/3] add noop test --- winlock/tests/it/main.rs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 winlock/tests/it/main.rs diff --git a/winlock/tests/it/main.rs b/winlock/tests/it/main.rs new file mode 100644 index 0000000..6793fc3 --- /dev/null +++ b/winlock/tests/it/main.rs @@ -0,0 +1,3 @@ +/// Only exists for now so that `cargo nextest run` doesn't fail until we add more actual tests. +#[test] +fn noop() {}