aptu_core/ai/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! AI integration module.
4//!
5//! Provides AI-assisted issue triage using multiple AI providers (Gemini, `OpenRouter`, Groq, Cerebras).
6
7pub mod circuit_breaker;
8pub mod client;
9pub mod context;
10pub mod models;
11pub mod provider;
12pub mod registry;
13pub mod types;
14
15pub use circuit_breaker::CircuitBreaker;
16pub use client::AiClient;
17pub use models::{AiModel, ModelProvider};
18pub use provider::AiProvider;
19pub use registry::{ProviderConfig, all_providers, get_provider};
20pub use types::{CreateIssueResponse, CreditsStatus, TriageResponse};
21
22use crate::history::AiStats;
23
24/// Cerebras API base URL (OpenAI-compatible endpoint).
25pub const CEREBRAS_API_URL: &str = "https://api.cerebras.ai/v1/chat/completions";
26
27/// Environment variable for Cerebras API key.
28pub const CEREBRAS_API_KEY_ENV: &str = "CEREBRAS_API_KEY";
29
30/// Gemini API base URL (OpenAI-compatible endpoint).
31pub const GEMINI_API_URL: &str =
32    "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions";
33
34/// Environment variable for Gemini API key.
35pub const GEMINI_API_KEY_ENV: &str = "GEMINI_API_KEY";
36
37/// Groq API base URL (OpenAI-compatible endpoint).
38pub const GROQ_API_URL: &str = "https://api.groq.com/openai/v1/chat/completions";
39
40/// Environment variable for Groq API key.
41pub const GROQ_API_KEY_ENV: &str = "GROQ_API_KEY";
42
43/// `OpenRouter` API base URL.
44pub const OPENROUTER_API_URL: &str = "https://openrouter.ai/api/v1/chat/completions";
45
46/// Environment variable for `OpenRouter` API key.
47pub const OPENROUTER_API_KEY_ENV: &str = "OPENROUTER_API_KEY";
48
49/// Response from AI analysis containing both triage data and usage stats.
50#[derive(Debug, Clone)]
51pub struct AiResponse {
52    /// The triage analysis result.
53    pub triage: TriageResponse,
54    /// AI usage statistics.
55    pub stats: AiStats,
56}
57
58/// Checks if a model is in the free tier (no cost).
59/// Free models on `OpenRouter` always have the `:free` suffix.
60#[must_use]
61pub fn is_free_model(model: &str) -> bool {
62    model.ends_with(":free")
63}
64
65/// Creates a formatted GitHub issue using AI assistance.
66///
67/// Takes raw issue title and body, formats them professionally using the configured AI provider.
68/// Returns formatted title, body, and suggested labels.
69///
70/// # Arguments
71///
72/// * `title` - Raw issue title from user
73/// * `body` - Raw issue body/description from user
74/// * `repo` - Repository name for context (owner/repo format)
75///
76/// # Errors
77///
78/// Returns an error if AI formatting fails or API is unavailable.
79pub async fn create_issue(
80    title: &str,
81    body: &str,
82    repo: &str,
83) -> anyhow::Result<(CreateIssueResponse, AiStats)> {
84    let config = crate::config::load_config()?;
85
86    // Create generic client for the configured provider
87    let client = AiClient::new(&config.ai.provider, &config.ai)?;
88    client.create_issue(title, body, repo).await
89}