diff --git a/src/error/mod.rs b/src/error/mod.rs index f6b1a55..c95ce5b 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -8,6 +8,8 @@ pub enum LLMError { BytesConversion(#[from] FromUtf8Error), #[error("cli not found: {0}")] CLINotFound(String), + #[error("empty credential: {0}")] + EmptyCredential(String), #[error("missing env variable: {0}")] Env(#[from] VarError), #[error("failed to prompt: {0}")] diff --git a/src/lib.rs b/src/lib.rs index 85e16d1..0572caa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,8 @@ mod error; pub mod llm; + +// Re-export constants for convenient access +pub use llm::constants; + +// Re-export Model trait and get_available_models function for convenient access +pub use llm::{get_available_models, Model}; diff --git a/src/llm/anthropic.rs b/src/llm/anthropic.rs index a0618c3..1572e40 100644 --- a/src/llm/anthropic.rs +++ b/src/llm/anthropic.rs @@ -1,16 +1,21 @@ use crate::error::LLMError; -use super::{Model, ModelFactory}; +use super::{Model, ModelFactory, constants::names}; use std::env::var; +const ANTHROPIC_API_KEY: &'static str = "ANTHROPIC_API_KEY"; + pub struct Anthropic { api_key: String, } impl ModelFactory for Anthropic { fn init() -> Result { - let api_key = var("ANTHROPIC_API_KEY")?; + let api_key = var(ANTHROPIC_API_KEY)?.trim().to_string(); + if api_key.is_empty() { + return Err(LLMError::EmptyCredential(ANTHROPIC_API_KEY.into())); + } Ok(Self { api_key }) } @@ -18,7 +23,7 @@ impl ModelFactory for Anthropic { impl Model for Anthropic { fn get_name(&self) -> String { - "Anthropic API".into() + names::ANTHROPIC.into() } fn prompt(&self, _: &str) -> Result { diff --git a/src/llm/claude.rs b/src/llm/claude.rs index 5432fc8..b003651 100644 --- a/src/llm/claude.rs +++ b/src/llm/claude.rs @@ -2,10 +2,10 @@ use std::process::Command; use crate::{ error::LLMError, - llm::{Model, ModelFactory}, + llm::{constants::names, Model, ModelFactory}, }; -const CLAUDE_CLI_NAME: &str = "claude"; +const CLAUDE_CLI_NAME: &'static str = "claude"; pub struct Claude {} @@ -22,7 +22,7 @@ impl ModelFactory for Claude { impl Model for Claude { fn get_name(&self) -> String { - "Claude CLI".into() + names::CLAUDE.into() } fn prompt(&self, input: &str) -> Result { diff --git a/src/llm/constants.rs b/src/llm/constants.rs new file mode 100644 index 0000000..b52cc8b --- /dev/null +++ b/src/llm/constants.rs @@ -0,0 +1,17 @@ +/// Model name constants that can be used to check against +/// model names in external projects. +/// +/// These constants match the values returned by `Model::get_name()`. +pub mod names { + /// The display name for the Cursor model: "Cursor CLI" + pub const CURSOR: &'static str = "Cursor CLI"; + + /// The display name for the Claude model: "Claude CLI" + pub const CLAUDE: &'static str = "Claude CLI"; + + /// The display name for the Anthropic model: "Anthropic API" + pub const ANTHROPIC: &'static str = "Anthropic API"; + + /// The display name for the OpenAI model: "OpenAI API" + pub const OPENAI: &'static str = "OpenAI API"; +} diff --git a/src/llm/cursor.rs b/src/llm/cursor.rs index 2a9c7fb..326dbd2 100644 --- a/src/llm/cursor.rs +++ b/src/llm/cursor.rs @@ -1,11 +1,11 @@ use crate::{ error::LLMError, - llm::{Model, ModelFactory}, + llm::{constants::names, Model, ModelFactory}, }; use std::process::Command; -const CURSOR_CLI_NAME: &str = "cursor-agent"; +const CURSOR_CLI_NAME: &'static str = "cursor-agent"; pub struct CursorCLI {} @@ -22,7 +22,7 @@ impl ModelFactory for CursorCLI { impl Model for CursorCLI { fn get_name(&self) -> String { - "Cursor CLI".into() + names::CURSOR.into() } fn prompt(&self, input: &str) -> Result { diff --git a/src/llm/mod.rs b/src/llm/mod.rs index 09aea66..bb855d7 100644 --- a/src/llm/mod.rs +++ b/src/llm/mod.rs @@ -6,6 +6,10 @@ mod claude; mod cursor; mod openai; +/// Model name constants that can be used to check against +/// model names in external projects. +pub mod constants; + use crate::{ error::LLMError, llm::{anthropic::Anthropic, claude::Claude, cursor::CursorCLI, openai::OpenAI}, @@ -35,6 +39,9 @@ pub trait Model: Send + Sync { /// Returns the available models in the current /// system context. +/// +/// TODO: ideally this should also include the lifeness check for the found models (e.g. sending a +/// simple prompt). pub fn get_available_models() -> Result>, LLMError> { let mut models: Vec> = vec![]; diff --git a/src/llm/openai.rs b/src/llm/openai.rs index 383a0b3..82787c5 100644 --- a/src/llm/openai.rs +++ b/src/llm/openai.rs @@ -2,23 +2,29 @@ use std::env::var; use crate::{ error::LLMError, - llm::{Model, ModelFactory}, + llm::{Model, ModelFactory, constants::names}, }; +const OPENAI_API_KEY: &'static str = "OPENAI_API_KEY"; + pub struct OpenAI { api_key: String, } impl ModelFactory for OpenAI { fn init() -> Result { - let api_key = var("OPENAI_API_KEY")?; + let api_key = var(OPENAI_API_KEY)?.trim().to_string(); + if api_key.is_empty() { + return Err(LLMError::EmptyCredential(OPENAI_API_KEY.into())); + } + Ok(Self { api_key }) } } impl Model for OpenAI { fn get_name(&self) -> String { - "OpenAI API".into() + names::OPENAI.into() } fn prompt(&self, _: &str) -> Result {