1mod base;
2
3use std::ops::Deref;
4use std::path::Path;
5
6use crate::cancellation::CancellationToken;
7use crate::config::LlmSettings;
8use crate::model::ChatMessage;
9
10pub(crate) use base::ProviderStreamEvent;
11pub use base::{
12 parse_sse, ProviderError, ProviderModel, ProviderTurn, SseParseResult, PROVIDER_TIMEOUT,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub(crate) enum ProviderFailureKind {
17 Cancelled,
18 ContextOverflow,
19 Timeout,
20 RateLimited,
21 Authentication,
22 InvalidRequest,
23 Transient,
24 Other,
25}
26
27impl ProviderError {
28 pub(crate) fn kind(&self) -> ProviderFailureKind {
29 if self.is_cancelled() {
30 return ProviderFailureKind::Cancelled;
31 }
32 let message = self.to_string().to_ascii_lowercase();
33 if contains_context_overflow(&message) {
34 return ProviderFailureKind::ContextOverflow;
35 }
36 if message.contains("http status 401") || message.contains("http status 403") {
37 return ProviderFailureKind::Authentication;
38 }
39 if message.contains("http status 429") {
40 return ProviderFailureKind::RateLimited;
41 }
42 if message.contains("(timeout)") || message.contains("http status 408") {
43 return ProviderFailureKind::Timeout;
44 }
45 if message.contains("http status 400")
46 || message.contains("http status 404")
47 || message.contains("http status 405")
48 || message.contains("http status 422")
49 || message.contains("unsupported")
50 || message.contains("invalid request")
51 {
52 return ProviderFailureKind::InvalidRequest;
53 }
54 if message.contains("(connection)")
55 || message.contains("(body)")
56 || message.contains("(decode)")
57 || message.contains("http status 500")
58 || message.contains("http status 502")
59 || message.contains("http status 503")
60 || message.contains("http status 504")
61 {
62 return ProviderFailureKind::Transient;
63 }
64 ProviderFailureKind::Other
65 }
66}
67
68fn contains_context_overflow(message: &str) -> bool {
69 [
70 "context window",
71 "context length",
72 "maximum context",
73 "max context",
74 "too many tokens",
75 "request too large",
76 "input is too long",
77 "exceeds the model context",
78 "http status 413",
79 ]
80 .iter()
81 .any(|needle| message.contains(needle))
82}
83
84pub struct Provider(base::Provider);
87
88impl Deref for Provider {
89 type Target = base::Provider;
90
91 fn deref(&self) -> &Self::Target {
92 &self.0
93 }
94}
95
96impl Provider {
97 pub fn new(settings: &LlmSettings) -> Result<Self, ProviderError> {
98 base::Provider::new(settings).map(Self)
99 }
100
101 pub fn new_codex(home: &Path, settings: &LlmSettings) -> Result<Self, ProviderError> {
102 base::Provider::new_codex(home, settings).map(Self)
103 }
104
105 pub(crate) fn with_session_id(self, session_id: &str) -> Self {
106 Self(self.0.with_session_id(session_id))
107 }
108
109 pub(crate) fn summarize_prepared(
110 &self,
111 planned: Vec<ChatMessage>,
112 cancellation: &CancellationToken,
113 ) -> Result<String, ProviderError> {
114 let attempts = crate::compaction_fallback::summary_attempts(planned, None)
118 .map_err(ProviderError::new)?;
119 let mut attempts = attempts.into_iter().peekable();
120 while let Some(attempt) = attempts.next() {
121 match self.0.summarize(&attempt, cancellation) {
122 Err(error)
123 if error.kind() == ProviderFailureKind::ContextOverflow
124 && attempts.peek().is_some() =>
125 {
126 continue;
127 }
128 result => return result,
129 }
130 }
131 Err(ProviderError::new(
132 "compaction exhausted every bounded provider attempt",
133 ))
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn classifies_provider_failures_for_recovery() {
143 assert_eq!(
144 ProviderError::new("request exceeds the model context window").kind(),
145 ProviderFailureKind::ContextOverflow
146 );
147 assert_eq!(
148 ProviderError::new("provider returned HTTP status 429").kind(),
149 ProviderFailureKind::RateLimited
150 );
151 assert_eq!(
152 ProviderError::new("provider request failed (timeout)").kind(),
153 ProviderFailureKind::Timeout
154 );
155 assert_eq!(
156 ProviderError::new("provider returned HTTP status 401").kind(),
157 ProviderFailureKind::Authentication
158 );
159 assert_eq!(
160 ProviderError::new("provider request failed (connection)").kind(),
161 ProviderFailureKind::Transient
162 );
163 }
164}