Skip to main content

systemprompt_ai/error/
mod.rs

1//! Typed error hierarchy for the [`systemprompt-ai`](crate) crate.
2//!
3//! [`AiError`] is the top-level public error returned by [`crate::services`].
4//! It composes the canonical repository error ([`RepositoryError`]) returned
5//! by every `*Repository` type in [`crate::repository`] via `#[from]`, plus
6//! common transport / parsing errors ([`reqwest::Error`],
7//! [`serde_json::Error`]).
8//!
9//! All public service signatures use [`Result<T>`] (i.e. `Result<T, AiError>`).
10//! The dyn `AiProvider` seam returns
11//! [`AiInferenceError`](systemprompt_models::errors::AiInferenceError); the
12//! `From<AiError>` impl in `inference` is the single mapping onto it.
13//!
14//! Copyright (c) systemprompt.io — Business Source License 1.1.
15//! See <https://systemprompt.io> for licensing details.
16
17mod 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/// An optional provider feature a request can depend on.
191#[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>;