Skip to main content

composio_sdk/models/
toolkits.rs

1//! Toolkits management
2//!
3//! This module provides functionality to manage toolkits in Composio.
4//! Toolkits are collections of tools that can be used to perform various tasks.
5//! They're conceptualized as a set of tools. Ex: Github toolkit can perform
6//! Github actions via its collection of tools.
7//!
8//! # Overview
9//!
10//! Toolkits represent integration points with external services. Each toolkit
11//! contains a collection of related tools and triggers.
12
13use serde::{Deserialize, Serialize};
14
15/// Toolkit list parameters
16#[derive(Debug, Clone, Default, Serialize, Deserialize)]
17pub struct ToolkitListParams {
18    /// Filter by category
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub category: Option<String>,
21
22    /// Pagination cursor
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub cursor: Option<String>,
25
26    /// Maximum number of results
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub limit: Option<u32>,
29
30    /// Sort by usage or alphabetically
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub sort_by: Option<SortBy>,
33
34    /// Filter by management type
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub managed_by: Option<ManagedBy>,
37
38    /// Search query
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub search: Option<String>,
41
42    /// Show deprecated toolkits
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub show_deprecated: Option<bool>,
45}
46
47/// Sort by options
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "lowercase")]
50pub enum SortBy {
51    /// Sort by usage
52    Usage,
53    /// Sort alphabetically
54    Alphabetically,
55}
56
57/// Managed by options
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "lowercase")]
60pub enum ManagedBy {
61    /// Composio-managed toolkits
62    Composio,
63    /// All toolkits
64    All,
65    /// Project-managed toolkits
66    Project,
67}
68
69/// Toolkit list response
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ToolkitListResponse {
72    /// List of toolkits
73    pub items: Vec<ToolkitItem>,
74
75    /// Next cursor for pagination
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub next_cursor: Option<String>,
78
79    /// Total number of pages
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub total_pages: Option<u32>,
82
83    /// Current page number
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub current_page: Option<u32>,
86
87    /// Total number of items
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub total_items: Option<u32>,
90}
91
92/// Toolkit item in list response
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ToolkitItem {
95    /// Toolkit slug
96    pub slug: String,
97
98    /// Toolkit name
99    pub name: String,
100
101    /// Toolkit description
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub description: Option<String>,
104
105    /// Toolkit logo URL
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub logo: Option<String>,
108
109    /// Supported authentication schemes
110    #[serde(default)]
111    pub auth_schemes: Vec<String>,
112
113    /// Composio-managed authentication schemes
114    #[serde(default)]
115    pub composio_managed_auth_schemes: Vec<String>,
116
117    /// Whether authentication is required
118    #[serde(default)]
119    pub no_auth: bool,
120
121    /// Toolkit metadata
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub meta: Option<ToolkitMeta>,
124
125    /// Whether this is a local toolkit (deprecated)
126    #[serde(default)]
127    pub is_local_toolkit: bool,
128}
129
130/// Toolkit metadata
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct ToolkitMeta {
133    /// Toolkit description
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub description: Option<String>,
136
137    /// Toolkit logo URL
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub logo: Option<String>,
140
141    /// Toolkit categories
142    #[serde(default)]
143    pub categories: Vec<String>,
144
145    /// Number of tools
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub tools_count: Option<u32>,
148
149    /// Number of triggers
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub triggers_count: Option<u32>,
152
153    /// Toolkit version
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub version: Option<String>,
156}
157
158/// Toolkit retrieve parameters
159#[derive(Debug, Clone, Default, Serialize, Deserialize)]
160pub struct ToolkitRetrieveParams {
161    /// Optional toolkit version
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub version: Option<String>,
164}
165
166/// Toolkit retrieve response
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct ToolkitRetrieveResponse {
169    /// Toolkit slug
170    pub slug: String,
171
172    /// Toolkit name
173    pub name: String,
174
175    /// Toolkit description
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub description: Option<String>,
178
179    /// Toolkit logo URL
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub logo: Option<String>,
182
183    /// Supported authentication schemes
184    #[serde(default)]
185    pub auth_schemes: Vec<String>,
186
187    /// Composio-managed authentication schemes
188    #[serde(default)]
189    pub composio_managed_auth_schemes: Vec<String>,
190
191    /// Whether authentication is required
192    #[serde(default)]
193    pub no_auth: bool,
194
195    /// Toolkit metadata
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub meta: Option<ToolkitMeta>,
198
199    /// Authentication configuration details
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub auth_config_details: Option<Vec<AuthConfigDetail>>,
202
203    /// Base URL for API requests
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub base_url: Option<String>,
206
207    /// Endpoint to get current user
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub get_current_user_endpoint: Option<String>,
210}
211
212/// Authentication configuration detail
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct AuthConfigDetail {
215    /// Authentication mode/scheme
216    pub mode: String,
217
218    /// Authentication fields
219    pub fields: AuthConfigFields,
220
221    /// Whether this is the default auth scheme
222    #[serde(default)]
223    pub is_default: bool,
224}
225
226/// Authentication configuration fields
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct AuthConfigFields {
229    /// Fields for connected account initiation
230    pub connected_account_initiation: AuthFieldSet,
231
232    /// Fields for auth config creation
233    pub auth_config_creation: AuthFieldSet,
234}
235
236/// Set of authentication fields
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct AuthFieldSet {
239    /// Required fields
240    #[serde(default)]
241    pub required: Vec<AuthField>,
242
243    /// Optional fields
244    #[serde(default)]
245    pub optional: Vec<AuthField>,
246}
247
248/// Authentication field definition
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct AuthField {
251    /// Field name
252    pub name: String,
253
254    /// Field display name
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub display_name: Option<String>,
257
258    /// Field description
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub description: Option<String>,
261
262    /// Field type
263    #[serde(rename = "type")]
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub field_type: Option<String>,
266
267    /// Whether field is required
268    #[serde(default)]
269    pub required: bool,
270
271    /// Default value
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub default: Option<serde_json::Value>,
274
275    /// Expected values
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub expected_values: Option<Vec<String>>,
278}
279
280/// Toolkit categories response
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct ToolkitCategoriesResponse {
283    /// List of categories
284    pub items: Vec<ToolkitCategory>,
285}
286
287/// Toolkit category
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct ToolkitCategory {
290    /// Category name
291    pub name: String,
292
293    /// Category display name
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub display_name: Option<String>,
296
297    /// Number of toolkits in this category
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub count: Option<u32>,
300}
301
302/// Authorization parameters
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct AuthorizeParams {
305    /// User ID to authorize
306    pub user_id: String,
307
308    /// Toolkit slug
309    pub toolkit: String,
310
311    /// Optional auth config ID (if not provided, will be auto-created)
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub auth_config_id: Option<String>,
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn test_toolkit_list_params_default() {
322        let params = ToolkitListParams::default();
323        assert!(params.category.is_none());
324        assert!(params.cursor.is_none());
325        assert!(params.limit.is_none());
326    }
327
328    #[test]
329    fn test_sort_by_serialization() {
330        let sort = SortBy::Usage;
331        let json = serde_json::to_string(&sort).unwrap();
332        assert_eq!(json, "\"usage\"");
333
334        let sort = SortBy::Alphabetically;
335        let json = serde_json::to_string(&sort).unwrap();
336        assert_eq!(json, "\"alphabetically\"");
337    }
338
339    #[test]
340    fn test_managed_by_serialization() {
341        let managed = ManagedBy::Composio;
342        let json = serde_json::to_string(&managed).unwrap();
343        assert_eq!(json, "\"composio\"");
344
345        let managed = ManagedBy::All;
346        let json = serde_json::to_string(&managed).unwrap();
347        assert_eq!(json, "\"all\"");
348
349        let managed = ManagedBy::Project;
350        let json = serde_json::to_string(&managed).unwrap();
351        assert_eq!(json, "\"project\"");
352    }
353
354    #[test]
355    fn test_toolkit_item_deserialization() {
356        let json = r#"{
357            "slug": "github",
358            "name": "GitHub",
359            "description": "GitHub integration",
360            "logo": "https://example.com/logo.png",
361            "auth_schemes": ["OAUTH2"],
362            "composio_managed_auth_schemes": ["OAUTH2"],
363            "no_auth": false,
364            "is_local_toolkit": false
365        }"#;
366
367        let item: ToolkitItem = serde_json::from_str(json).unwrap();
368        assert_eq!(item.slug, "github");
369        assert_eq!(item.name, "GitHub");
370        assert_eq!(item.auth_schemes.len(), 1);
371        assert!(!item.no_auth);
372    }
373
374    #[test]
375    fn test_toolkit_meta() {
376        let meta = ToolkitMeta {
377            description: Some("Test toolkit".to_string()),
378            logo: Some("https://example.com/logo.png".to_string()),
379            categories: vec!["development".to_string()],
380            tools_count: Some(50),
381            triggers_count: Some(10),
382            version: Some("1.0.0".to_string()),
383        };
384
385        assert_eq!(meta.tools_count, Some(50));
386        assert_eq!(meta.triggers_count, Some(10));
387        assert_eq!(meta.categories.len(), 1);
388    }
389
390    #[test]
391    fn test_auth_field() {
392        let field = AuthField {
393            name: "client_id".to_string(),
394            display_name: Some("Client ID".to_string()),
395            description: Some("OAuth client ID".to_string()),
396            field_type: Some("string".to_string()),
397            required: true,
398            default: None,
399            expected_values: None,
400        };
401
402        assert_eq!(field.name, "client_id");
403        assert!(field.required);
404    }
405
406    #[test]
407    fn test_auth_field_set() {
408        let field_set = AuthFieldSet {
409            required: vec![AuthField {
410                name: "api_key".to_string(),
411                display_name: None,
412                description: None,
413                field_type: Some("string".to_string()),
414                required: true,
415                default: None,
416                expected_values: None,
417            }],
418            optional: vec![],
419        };
420
421        assert_eq!(field_set.required.len(), 1);
422        assert_eq!(field_set.optional.len(), 0);
423    }
424
425    #[test]
426    fn test_toolkit_retrieve_params_serialization() {
427        let params = ToolkitRetrieveParams {
428            version: Some("20250906_01".to_string()),
429        };
430
431        let value = serde_json::to_value(&params).unwrap();
432        assert_eq!(value["version"], "20250906_01");
433    }
434
435    #[test]
436    fn test_toolkit_retrieve_response_deserialization() {
437        let json = r#"{
438            "slug": "github",
439            "name": "GitHub",
440            "auth_schemes": ["OAUTH2"],
441            "composio_managed_auth_schemes": ["OAUTH2"],
442            "no_auth": false
443        }"#;
444
445        let response: ToolkitRetrieveResponse = serde_json::from_str(json).unwrap();
446        assert_eq!(response.slug, "github");
447        assert_eq!(response.name, "GitHub");
448        assert_eq!(response.auth_schemes.len(), 1);
449    }
450
451    #[test]
452    fn test_authorize_params() {
453        let params = AuthorizeParams {
454            user_id: "user_123".to_string(),
455            toolkit: "github".to_string(),
456            auth_config_id: Some("ac_456".to_string()),
457        };
458
459        assert_eq!(params.user_id, "user_123");
460        assert_eq!(params.toolkit, "github");
461        assert!(params.auth_config_id.is_some());
462    }
463}