rainy_sdk/endpoints/
cowork.rs

1//! Cowork endpoint for retrieving subscription capabilities
2//!
3//! This endpoint validates the API key and returns the user's
4//! Cowork plan, available models, and feature access.
5
6use crate::{
7    cowork::{CoworkCapabilities, CoworkFeatures, CoworkPlan, CoworkUsage},
8    error::Result,
9    RainyClient,
10};
11use serde::Deserialize;
12
13/// Response from the Cowork capabilities API
14#[derive(Debug, Clone, Deserialize)]
15struct CoworkCapabilitiesResponse {
16    plan: CoworkPlan,
17    plan_name: String,
18    is_valid: bool,
19    usage: CoworkUsage,
20    models: Vec<String>,
21    features: CoworkFeatures,
22    #[serde(default)]
23    upgrade_message: Option<String>,
24}
25
26impl RainyClient {
27    /// Retrieve Cowork capabilities for the current API key.
28    ///
29    /// This method validates the API key and returns information about:
30    /// - Subscription plan (Free, GoPlus, Plus, Pro, ProPlus)
31    /// - Available AI models
32    /// - Feature access (web research, document export, etc.)
33    /// - Usage tracking
34    ///
35    /// # Returns
36    ///
37    /// A `Result` containing `CoworkCapabilities` on success, or a `RainyError` on failure.
38    ///
39    /// # Example
40    ///
41    /// ```rust,no_run
42    /// # use rainy_sdk::RainyClient;
43    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
44    /// let client = RainyClient::with_api_key("your-api-key")?;
45    /// let caps = client.get_cowork_capabilities().await?;
46    ///
47    /// if caps.plan.is_paid() {
48    ///     println!("Paid plan with {} models available", caps.models.len());
49    /// } else {
50    ///     println!("Free plan - upgrade for more features");
51    /// }
52    /// # Ok(())
53    /// # }
54    /// ```
55    pub async fn get_cowork_capabilities(&self) -> Result<CoworkCapabilities> {
56        let url = format!("{}/api/v1/cowork/capabilities", self.auth_config().base_url);
57
58        let response = self.http_client().get(&url).send().await;
59
60        match response {
61            Ok(resp) => {
62                if resp.status().is_success() {
63                    let caps_response: CoworkCapabilitiesResponse =
64                        self.handle_response(resp).await?;
65
66                    Ok(CoworkCapabilities {
67                        plan: caps_response.plan,
68                        plan_name: caps_response.plan_name,
69                        is_valid: caps_response.is_valid,
70                        usage: caps_response.usage,
71                        models: caps_response.models,
72                        features: caps_response.features,
73                        upgrade_message: caps_response.upgrade_message,
74                    })
75                } else {
76                    // Invalid or expired API key - return free plan
77                    Ok(CoworkCapabilities::free())
78                }
79            }
80            Err(_) => {
81                // Network error or API unavailable - return free plan
82                Ok(CoworkCapabilities::free())
83            }
84        }
85    }
86
87    /// Check if the current API key grants paid access.
88    ///
89    /// This is a convenience method that calls `get_cowork_capabilities()`
90    /// and checks if the plan is not Free.
91    ///
92    /// # Returns
93    ///
94    /// `true` if the user has a paid plan (GoPlus, Plus, Pro, or ProPlus).
95    pub async fn has_paid_plan(&self) -> bool {
96        match self.get_cowork_capabilities().await {
97            Ok(caps) => caps.plan.is_paid(),
98            Err(_) => false,
99        }
100    }
101
102    /// Get available models for Cowork based on subscription plan.
103    ///
104    /// # Returns
105    ///
106    /// A vector of model identifiers available for the current plan.
107    pub async fn get_cowork_models(&self) -> Result<Vec<String>> {
108        let caps = self.get_cowork_capabilities().await?;
109        Ok(caps.models)
110    }
111
112    /// Check if a specific feature is available.
113    ///
114    /// # Arguments
115    ///
116    /// * `feature` - Feature name: "web_research", "document_export", "image_analysis", etc.
117    ///
118    /// # Returns
119    ///
120    /// `true` if the feature is available for the current plan.
121    pub async fn can_use_feature(&self, feature: &str) -> bool {
122        match self.get_cowork_capabilities().await {
123            Ok(caps) => caps.can_use_feature(feature),
124            Err(_) => false,
125        }
126    }
127
128    /// Check if a specific model is available for the current plan.
129    ///
130    /// # Arguments
131    ///
132    /// * `model` - The model identifier to check.
133    ///
134    /// # Returns
135    ///
136    /// `true` if the model is available for the current plan.
137    pub async fn can_use_model(&self, model: &str) -> bool {
138        match self.get_cowork_capabilities().await {
139            Ok(caps) => caps.can_use_model(model),
140            Err(_) => false,
141        }
142    }
143
144    /// Check if user can make another request based on usage limits.
145    pub async fn can_make_request(&self) -> bool {
146        match self.get_cowork_capabilities().await {
147            Ok(caps) => caps.can_make_request(),
148            Err(_) => false,
149        }
150    }
151}
152
153/// Offline capabilities based on cached plan data.
154///
155/// This can be used when network is unavailable to provide
156/// a degraded experience based on previously cached plan info.
157pub fn get_offline_capabilities(cached_plan: Option<CoworkPlan>) -> CoworkCapabilities {
158    match cached_plan {
159        Some(CoworkPlan::ProPlus)
160        | Some(CoworkPlan::Pro)
161        | Some(CoworkPlan::Plus)
162        | Some(CoworkPlan::GoPlus) => {
163            // For paid plans, we can't know exact capabilities offline
164            // Return a minimal valid state
165            CoworkCapabilities {
166                plan: cached_plan.unwrap(),
167                plan_name: cached_plan.unwrap().display_name().to_string(),
168                is_valid: true,
169                usage: CoworkUsage::default(),
170                models: vec!["gemini-2.5-flash".to_string()],
171                features: CoworkFeatures::default(),
172                upgrade_message: None,
173            }
174        }
175        _ => CoworkCapabilities::free(),
176    }
177}