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}