@@ -9,13 +9,17 @@ use serde::{Deserialize, Serialize};
99use std:: collections:: HashMap ;
1010use std:: path:: Path ;
1111use std:: process:: Command ;
12+ use std:: time:: Duration ;
13+ use tokio:: time:: sleep;
1214
1315use crate :: utils:: diagnostics:: { DiagnosticCoordinator , DiagnosticResults , IssueSeverity , IssueCategory } ;
1416
15- /// OpenAI API client for AI-powered analysis
17+ /// OpenAI API client for AI-powered analysis with rate limiting and retry logic
1618pub struct OpenAIClient {
1719 api_key : String ,
1820 client : reqwest:: Client ,
21+ max_retries : u32 ,
22+ base_delay : Duration ,
1923}
2024
2125/// AI analysis result from OpenAI
@@ -36,16 +40,46 @@ pub struct AIEnhancedFinding {
3640}
3741
3842impl OpenAIClient {
39- /// Create a new OpenAI client
43+ /// Create a new OpenAI client with retry and rate limiting
4044 pub fn new ( api_key : String ) -> Self {
4145 Self {
4246 api_key,
43- client : reqwest:: Client :: new ( ) ,
47+ client : reqwest:: Client :: builder ( )
48+ . timeout ( Duration :: from_secs ( 30 ) )
49+ . build ( )
50+ . unwrap_or_else ( |_| reqwest:: Client :: new ( ) ) ,
51+ max_retries : 3 ,
52+ base_delay : Duration :: from_millis ( 1000 ) ,
4453 }
4554 }
4655
47- /// Analyze a security finding using OpenAI
56+ /// Analyze a security finding using OpenAI with retry logic
4857 pub async fn analyze_finding ( & self , finding : & AuditFinding ) -> Result < AIAnalysis > {
58+ for attempt in 0 ..=self . max_retries {
59+ match self . try_analyze_finding ( finding) . await {
60+ Ok ( analysis) => return Ok ( analysis) ,
61+ Err ( e) if attempt < self . max_retries => {
62+ // Check if it's a rate limit error
63+ if e. to_string ( ) . contains ( "rate limit" ) || e. to_string ( ) . contains ( "429" ) {
64+ let delay = self . base_delay * 2_u32 . pow ( attempt) ;
65+ eprintln ! ( "⏳ Rate limited, retrying in {}ms... (attempt {}/{})" ,
66+ delay. as_millis( ) , attempt + 1 , self . max_retries) ;
67+ sleep ( delay) . await ;
68+ } else {
69+ eprintln ! ( "⚠️ OpenAI API error, retrying... (attempt {}/{}): {}" ,
70+ attempt + 1 , self . max_retries, e) ;
71+ sleep ( self . base_delay ) . await ;
72+ }
73+ } ,
74+ Err ( e) => return Err ( e) ,
75+ }
76+ }
77+
78+ Err ( anyhow:: anyhow!( "OpenAI analysis failed after {} retries" , self . max_retries) )
79+ }
80+
81+ /// Single attempt to analyze a finding
82+ async fn try_analyze_finding ( & self , finding : & AuditFinding ) -> Result < AIAnalysis > {
4983 let prompt = format ! (
5084 "Analyze this security finding and provide enhanced insights:\n \n \
5185 Title: {}\n \
@@ -123,8 +157,32 @@ impl OpenAIClient {
123157 Ok ( ai_analysis)
124158 }
125159
126- /// Analyze code content for security issues
160+ /// Analyze code content for security issues with retry logic
127161 pub async fn analyze_code ( & self , code_content : & str , file_path : & str ) -> Result < Vec < AuditFinding > > {
162+ for attempt in 0 ..=self . max_retries {
163+ match self . try_analyze_code ( code_content, file_path) . await {
164+ Ok ( findings) => return Ok ( findings) ,
165+ Err ( e) if attempt < self . max_retries => {
166+ if e. to_string ( ) . contains ( "rate limit" ) || e. to_string ( ) . contains ( "429" ) {
167+ let delay = self . base_delay * 2_u32 . pow ( attempt) ;
168+ eprintln ! ( "⏳ Rate limited during code analysis, retrying in {}ms... (attempt {}/{})" ,
169+ delay. as_millis( ) , attempt + 1 , self . max_retries) ;
170+ sleep ( delay) . await ;
171+ } else {
172+ eprintln ! ( "⚠️ OpenAI code analysis error, retrying... (attempt {}/{}): {}" ,
173+ attempt + 1 , self . max_retries, e) ;
174+ sleep ( self . base_delay ) . await ;
175+ }
176+ } ,
177+ Err ( e) => return Err ( e) ,
178+ }
179+ }
180+
181+ Err ( anyhow:: anyhow!( "OpenAI code analysis failed after {} retries" , self . max_retries) )
182+ }
183+
184+ /// Single attempt to analyze code
185+ async fn try_analyze_code ( & self , code_content : & str , file_path : & str ) -> Result < Vec < AuditFinding > > {
128186 let prompt = format ! (
129187 "Analyze this Rust code file for security vulnerabilities:\n \n \
130188 File: {}\n \
0 commit comments