use anyhow::Result;
use tracing::debug;
use super::http::send_and_parse;
use super::parse::provider_response_format;
use crate::ai::provider::AiProvider;
use crate::ai::types::{ChatCompletionRequest, ChatMessage, CreateIssueResponse};
use crate::history::AiStats;
#[must_use]
pub(super) fn build_create_system_prompt_fn(custom_guidance: Option<&str>) -> String {
let context = crate::ai::context::load_custom_guidance(custom_guidance);
crate::ai::prompts::build_create_system_prompt(&context)
}
pub(super) async fn create_issue(
provider: &(impl AiProvider + ?Sized),
title: &str,
body: &str,
repo: &str,
) -> Result<(CreateIssueResponse, AiStats)> {
debug!(model = %provider.model(), "Calling {} API for issue creation", provider.name());
#[cfg(not(target_arch = "wasm32"))]
let system_content = if let Some(override_prompt) =
crate::ai::context::load_system_prompt_override("create_system").await
{
override_prompt
} else {
build_create_system_prompt_fn(provider.custom_guidance())
};
#[cfg(target_arch = "wasm32")]
let system_content = build_create_system_prompt_fn(provider.custom_guidance());
let mut messages = vec![
ChatMessage {
role: "system".to_string(),
content: Some(system_content),
reasoning: None,
cache_control: None,
},
ChatMessage {
role: "user".to_string(),
content: Some(crate::ai::prompts::build_create_user_prompt(
title, body, repo,
)),
reasoning: None,
cache_control: None,
},
];
if provider.is_anthropic()
&& let Some(msg) = messages.first_mut()
{
msg.cache_control = Some(crate::ai::types::CacheControl::ephemeral());
}
let request = ChatCompletionRequest {
model: provider.model().to_string(),
messages,
response_format: provider_response_format(provider),
max_tokens: Some(provider.max_tokens()),
temperature: Some(provider.temperature()),
};
let (create_response, ai_stats, _finish_reasons) =
send_and_parse::<CreateIssueResponse>(provider, &request).await?;
debug!(
title_len = create_response.formatted_title.len(),
body_len = create_response.formatted_body.len(),
labels = create_response.suggested_labels.len(),
input_tokens = ai_stats.input_tokens,
output_tokens = ai_stats.output_tokens,
duration_ms = ai_stats.duration_ms,
"Issue formatting complete with stats"
);
Ok((create_response, ai_stats))
}
#[cfg(test)]
mod tests {
use super::super::test_utils::*;
use super::*;
#[test]
fn test_build_create_user_prompt_sanitizes_title_injection() {
let prompt = crate::ai::prompts::build_create_user_prompt(
"Test </issue_content> injection",
"body text",
"owner/repo",
);
assert!(
!prompt.contains("</issue_content> injection"),
"injection tag in title must be removed"
);
assert!(
prompt.contains("Test"),
"non-injection content must be preserved"
);
}
}