systemprompt_ai/error/
mod.rs1mod inference;
18mod response;
19
20use std::time::Duration;
21
22use thiserror::Error;
23
24use systemprompt_identifiers::{McpServerId, McpToolName};
25use systemprompt_traits::{AiProviderError, FileStorageError, RepositoryError};
26use systemprompt_wire::error::WireStreamError;
27
28use crate::services::config::AiConfigError;
29use crate::services::storage::StorageConfigError;
30
31#[derive(Debug, Error)]
32pub enum AiError {
33 #[error("Model not specified and no default available for provider {provider}")]
34 ModelNotSpecified { provider: String },
35
36 #[error("Request metadata missing required field: {field}")]
37 MissingMetadata { field: String },
38
39 #[error("User context required for billing and audit trails")]
40 MissingUserContext,
41
42 #[error("Provider {provider} returned empty response")]
43 EmptyProviderResponse { provider: String },
44
45 #[error("Tool call schema validation failed: {reason}")]
46 InvalidToolSchema { reason: String },
47
48 #[error("Authentication required for service {service_id}")]
49 AuthenticationRequired { service_id: McpServerId },
50
51 #[error("Structured output validation failed after {retries} attempts: {details}")]
52 StructuredOutputFailed { retries: usize, details: String },
53
54 #[error("Provider {provider} error: {message}")]
55 ProviderError { provider: String, message: String },
56
57 #[error("No configured provider supports model {model}")]
58 NoProviderForModel { model: String },
59
60 #[error("Upstream provider could not be resolved: {0}")]
61 Upstream(#[from] crate::services::upstream::UpstreamTargetError),
62
63 #[error("Serialization failed: {0}")]
64 SerializationError(#[from] serde_json::Error),
65
66 #[error("HTTP request failed: {0}")]
67 Http(#[from] reqwest::Error),
68
69 #[error("I/O error: {0}")]
70 Io(#[from] std::io::Error),
71
72 #[error("Message history cannot be serialized to JSON")]
73 MessageSerializationFailed,
74
75 #[error("Tool {tool_name} missing required field: {field}")]
76 MissingToolField {
77 tool_name: McpToolName,
78 field: String,
79 },
80
81 #[error("Tool description cannot be empty for tool: {tool_name}")]
82 EmptyToolDescription { tool_name: McpToolName },
83
84 #[error("No tool calls found in provider response")]
85 NoToolCalls,
86
87 #[error("Rate limit exceeded for provider {provider}: {details}")]
88 RateLimit { provider: String, details: String },
89
90 #[error("Provider {provider} returned HTTP {status}: {body}")]
91 HttpStatus {
92 provider: String,
93 status: u16,
94 retry_after: Option<Duration>,
95 body: String,
96 },
97
98 #[error("Provider {provider} request timed out after {after_ms}ms")]
99 Timeout { provider: String, after_ms: u64 },
100
101 #[error("Circuit breaker open for provider {provider}; failing fast")]
102 CircuitOpen { provider: String },
103
104 #[error("Provider {provider} unavailable: concurrency limit reached")]
105 DependencyUnavailable { provider: String },
106
107 #[error("Invalid API credentials for provider {provider}")]
108 AuthenticationFailed { provider: String },
109
110 #[error("Configuration error: {message}")]
111 ConfigurationError { message: String },
112
113 #[error(transparent)]
114 Repository(#[from] RepositoryError),
115
116 #[error("file persistence failed: {0}")]
117 FilePersistence(#[from] AiProviderError),
118
119 #[error("MCP service {service_id} not found or not configured")]
120 McpServiceNotFound { service_id: McpServerId },
121
122 #[error("MCP service {service_id} requires OAuth authentication but no token available")]
123 McpAuthenticationMissing { service_id: McpServerId },
124
125 #[error("Failed to determine service authentication requirements: {details}")]
126 ServiceAuthCheckFailed { details: String },
127
128 #[error("{context}: {source}")]
129 Storage {
130 context: String,
131 #[source]
132 source: FileStorageError,
133 },
134
135 #[error("invalid image storage configuration: {0}")]
136 StorageConfig(#[from] StorageConfigError),
137
138 #[error("image size {size} bytes exceeds the maximum allowed size {max} bytes")]
139 ImageTooLarge { size: usize, max: usize },
140
141 #[error("failed to decode base64 image: {0}")]
142 ImageDecode(#[from] base64::DecodeError),
143
144 #[error("provider stream failed: {0}")]
145 Stream(#[from] WireStreamError),
146
147 #[error("invalid generated file id: {0}")]
148 InvalidFileId(#[source] uuid::Error),
149
150 #[error("Invalid input: {0}")]
151 InvalidInput(String),
152
153 #[error("Regex error: {0}")]
154 Regex(#[from] regex::Error),
155
156 #[error(transparent)]
157 ToolProvider(#[from] systemprompt_traits::ToolProviderError),
158
159 #[error("tool discovery incomplete: {0}")]
160 ToolDiscovery(String),
161
162 #[error("provider {provider} has no pricing for model {model}; refusing to bill it")]
163 UnknownModel { provider: String, model: String },
164
165 #[error(transparent)]
166 Secrets(#[from] systemprompt_config::SecretsBootstrapError),
167
168 #[error(transparent)]
169 WireParse(#[from] systemprompt_wire::error::WireParseError),
170
171 #[error(transparent)]
172 Config(#[from] AiConfigError),
173
174 #[error("provider {provider} is not configured")]
175 ProviderNotFound { provider: String },
176
177 #[error("provider {provider} is disabled")]
178 ProviderDisabled { provider: String },
179
180 #[error("provider {provider} does not support {capability}")]
181 CapabilityUnsupported {
182 provider: String,
183 capability: ProviderCapability,
184 },
185
186 #[error("no configured provider supports {capability}")]
187 NoProviderWithCapability { capability: ProviderCapability },
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum ProviderCapability {
193 Streaming,
194 ToolStreaming,
195 GoogleSearch,
196 ImageGeneration,
197}
198
199impl std::fmt::Display for ProviderCapability {
200 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201 f.write_str(match self {
202 Self::Streaming => "streaming",
203 Self::ToolStreaming => "tool streaming",
204 Self::GoogleSearch => "Google Search grounding",
205 Self::ImageGeneration => "image generation",
206 })
207 }
208}
209
210pub type Result<T> = std::result::Result<T, AiError>;