Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 60 additions & 49 deletions src/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use crate::multi_step_integration::{generate_commit_message_local, generate_comm
const INSTRUCTION_TEMPLATE: &str = include_str!("../resources/prompt.md");

/// Returns the instruction template for the AI model.
/// This template guides the model in generating appropriate commit messages.
///
/// # Returns
/// * `Result<String>` - The rendered template or an error
Expand Down Expand Up @@ -85,58 +84,32 @@ pub async fn generate(patch: String, remaining_tokens: usize, model: Model, sett
.and_then(|s| s.max_commit_length)
.or(config::APP_CONFIG.max_commit_length);

// Check if we have a valid API key configuration
let has_valid_api_key = if let Some(custom_settings) = settings {
custom_settings
.openai_api_key
.as_ref()
.map(|key| !key.is_empty() && key != "<PLACE HOLDER FOR YOUR API KEY>")
.unwrap_or(false)
} else {
// Check environment variable or config
config::APP_CONFIG
.openai_api_key
.as_ref()
.map(|key| !key.is_empty() && key != "<PLACE HOLDER FOR YOUR API KEY>")
.unwrap_or(false)
|| std::env::var("OPENAI_API_KEY")
.map(|key| !key.is_empty())
.unwrap_or(false)
};

if !has_valid_api_key {
bail!("OpenAI API key not configured. Please set your API key using:\n git-ai config set openai-api-key <your-key>\nor set the OPENAI_API_KEY environment variable.");
}

// Use custom settings if provided
if let Some(custom_settings) = settings {
if let Some(api_key) = &custom_settings.openai_api_key {
if !api_key.is_empty() && api_key != "<PLACE HOLDER FOR YOUR API KEY>" {
match openai::create_openai_config(custom_settings) {
Ok(config) => {
let client = Client::with_config(config);
let model_str = model.to_string();
// Always try to create config - this will handle API key validation and fallback
match openai::create_openai_config(custom_settings) {
Ok(config) => {
let client = Client::with_config(config);
let model_str = model.to_string();

match generate_commit_message_multi_step(&client, &model_str, &patch, max_length).await {
Ok(message) => return Ok(openai::Response { response: message }),
Err(e) => {
// Check if it's an API key error
if e.to_string().contains("invalid_api_key") || e.to_string().contains("Incorrect API key") {
bail!("Invalid OpenAI API key. Please check your API key configuration.");
}
log::warn!("Multi-step generation with custom settings failed: {e}");
if let Some(session) = debug_output::debug_session() {
session.set_multi_step_error(e.to_string());
}
}
}
}
match generate_commit_message_multi_step(&client, &model_str, &patch, max_length).await {
Ok(message) => return Ok(openai::Response { response: message }),
Err(e) => {
// If config creation fails due to API key, propagate the error
return Err(e);
// Check if it's an API key error
if e.to_string().contains("invalid_api_key") || e.to_string().contains("Incorrect API key") {
bail!("Invalid OpenAI API key. Set via:\n1. git-ai config set openai-api-key <key>\n2. OPENAI_API_KEY environment variable");
}
log::warn!("Multi-step generation with custom settings failed: {e}");
if let Some(session) = debug_output::debug_session() {
session.set_multi_step_error(e.to_string());
}
}
}
}
Err(e) => {
// If config creation fails due to API key, propagate the error
return Err(e);
}
}
} else {
// Try with default settings
Expand All @@ -150,7 +123,7 @@ pub async fn generate(patch: String, remaining_tokens: usize, model: Model, sett
Err(e) => {
// Check if it's an API key error
if e.to_string().contains("invalid_api_key") || e.to_string().contains("Incorrect API key") {
bail!("Invalid OpenAI API key. Please check your API key configuration.");
bail!("Invalid OpenAI API key. Set via:\n1. git-ai config set openai-api-key <key>\n2. OPENAI_API_KEY environment variable");
}
log::warn!("Multi-step generation failed: {e}");
if let Some(session) = debug_output::debug_session() {
Expand Down Expand Up @@ -247,7 +220,7 @@ mod tests {
assert!(result.is_err());
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains("OpenAI API key not configured"),
error_message.contains("OpenAI API key not found") || error_message.contains("git-ai config set openai-api-key"),
"Expected error message about missing API key, got: {}",
error_message
);
Expand Down Expand Up @@ -276,9 +249,47 @@ mod tests {
assert!(result.is_err());
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains("OpenAI API key not configured"),
error_message.contains("OpenAI API key not found") || error_message.contains("git-ai config set openai-api-key"),
"Expected error message about invalid API key, got: {}",
error_message
);
}

#[tokio::test]
async fn test_api_key_fallback_to_env() {
// Set a valid-looking API key in environment (won't actually work but should pass validation)
std::env::set_var("OPENAI_API_KEY", "sk-test123456789012345678901234567890123456789012345678");

// Create settings with placeholder API key - should fall back to env var
let settings = AppConfig {
openai_api_key: Some("<PLACE HOLDER FOR YOUR API KEY>".to_string()),
model: Some("gpt-4o-mini".to_string()),
max_tokens: Some(1024),
max_commit_length: Some(72),
timeout: Some(30)
};

// This should not fail immediately due to API key - it should use the env var
// (It will likely fail later due to invalid API key, but not in the validation stage)
let result = generate(
"diff --git a/test.txt b/test.txt\n+Hello World".to_string(),
1024,
Model::GPT41Mini,
Some(&settings)
)
.await;

// Clean up
std::env::remove_var("OPENAI_API_KEY");

// The error should NOT be about missing API key configuration
if let Err(e) = result {
let error_msg = e.to_string();
assert!(
!error_msg.contains("OpenAI API key not found"),
"Should not get 'key not found' error when env var is set, got: {}",
error_msg
);
}
}
}
30 changes: 23 additions & 7 deletions src/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,29 @@ pub async fn generate_commit_message(diff: &str) -> Result<String> {

/// Creates an OpenAI configuration from application settings
pub fn create_openai_config(settings: &AppConfig) -> Result<OpenAIConfig> {
let api_key = settings
.openai_api_key
.as_ref()
.ok_or_else(|| anyhow!("OpenAI API key not configured"))?;
// Try config first, then environment variable
let api_key = if let Some(key) = &settings.openai_api_key {
if !key.is_empty() && key != "<PLACE HOLDER FOR YOUR API KEY>" {
key.clone()
} else {
// Try environment variable as fallback
std::env::var("OPENAI_API_KEY")
.map_err(|_| anyhow!(
"OpenAI API key not found. Set via:\n1. git-ai config set openai-api-key <key>\n2. OPENAI_API_KEY environment variable"
))?
}
} else {
// No config key, try environment variable
std::env::var("OPENAI_API_KEY")
.map_err(|_| anyhow!(
"OpenAI API key not found. Set via:\n1. git-ai config set openai-api-key <key>\n2. OPENAI_API_KEY environment variable"
))?
};

if api_key.is_empty() || api_key == "<PLACE HOLDER FOR YOUR API KEY>" {
return Err(anyhow!("Invalid OpenAI API key"));
if api_key.is_empty() {
return Err(anyhow!(
"OpenAI API key cannot be empty. Set via:\n1. git-ai config set openai-api-key <key>\n2. OPENAI_API_KEY environment variable"
));
}

let config = OpenAIConfig::new().with_api_key(api_key);
Expand Down Expand Up @@ -342,7 +358,7 @@ pub async fn call_with_config(request: Request, config: OpenAIConfig) -> Result<
// Check if it's an API key error - fail immediately without retrying
if let OpenAIError::ApiError(ref api_err) = &last_error.as_ref().unwrap() {
if api_err.code.as_deref() == Some("invalid_api_key") {
let error_msg = format!("Invalid OpenAI API key: {}", api_err.message);
let error_msg = format!("Invalid OpenAI API key: {}. Set via:\n1. git-ai config set openai-api-key <key>\n2. OPENAI_API_KEY environment variable", api_err.message);
log::error!("{error_msg}");
return Err(anyhow!(error_msg));
}
Expand Down
4 changes: 2 additions & 2 deletions tests/model_validation_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ fn test_model_as_ref() {
s.as_ref().to_string()
}

assert_eq!(takes_str_ref(&Model::GPT41), "gpt-4.1");
assert_eq!(takes_str_ref(&Model::GPT41Mini), "gpt-4.1-mini");
assert_eq!(takes_str_ref(Model::GPT41), "gpt-4.1");
assert_eq!(takes_str_ref(Model::GPT41Mini), "gpt-4.1-mini");
}

#[test]
Expand Down
Loading