Skip to main content

flow_adapter_ai/
error.rs

1use thiserror::Error;
2
3#[derive(Debug, Error)]
4pub enum CloudAiError {
5    #[error("provider '{0}' not registered")]
6    ProviderNotFound(String),
7    #[error("missing API key for provider '{provider}'. Set {env_var} in your environment.")]
8    MissingApiKey { provider: String, env_var: String },
9    #[error("cloud AI is disabled by policy (allow_cloud_ai = false)")]
10    PolicyDenied,
11    #[error("provider '{provider}' is disabled in settings")]
12    ProviderDisabled { provider: String },
13    #[error("HTTP error from provider '{provider}': {reason}")]
14    Http { provider: String, reason: String },
15    #[error("provider '{provider}' returned status {status}: {body}")]
16    Status {
17        provider: String,
18        status: u16,
19        body: String,
20    },
21    #[error("failed to parse provider '{provider}' response: {reason}")]
22    Parse { provider: String, reason: String },
23    #[error("unexpected response shape from provider '{provider}': {detail}")]
24    Shape { provider: String, detail: String },
25    #[error("provider '{provider}' does not support {capability}")]
26    Unsupported { provider: String, capability: String },
27}
28
29/// Cap on `body` length when surfacing a provider's non-2xx response into
30/// `CloudAiError::Status`. The whole point is that this string flows back
31/// through `tauri::command -> Result<_, String>` to the renderer (and into
32/// logs along the way) - providers occasionally return multi-KB debug
33/// bodies, and an unbounded body inflates log lines and the renderer
34/// surface for no extra signal. The first ~1KB is enough to identify the
35/// failure class; anything longer is truncated with an explicit marker so
36/// the operator knows there was more.
37pub const MAX_ERROR_BODY_LEN: usize = 1024;
38
39/// Trim and length-cap a raw provider error body before stuffing it into
40/// `CloudAiError::Status`. Idempotent and safe on UTF-8: truncates at the
41/// last char boundary <= MAX_ERROR_BODY_LEN.
42pub fn redact_error_body(body: &str) -> String {
43    let trimmed = body.trim();
44    if trimmed.len() <= MAX_ERROR_BODY_LEN {
45        return trimmed.to_string();
46    }
47    let mut end = MAX_ERROR_BODY_LEN;
48    while end > 0 && !trimmed.is_char_boundary(end) {
49        end -= 1;
50    }
51    format!("{}…[truncated]", &trimmed[..end])
52}