2025-08-02 16:22:17 +08:00
from typing import List , Dict , Any , Optional
from pydantic import BaseModel
from utils . logger import logger
from . client import ComposioClient
2025-08-04 11:43:20 +08:00
class CategoryInfo ( BaseModel ) :
id : str
name : str
2025-08-02 16:22:17 +08:00
class ToolkitInfo ( BaseModel ) :
slug : str
name : str
description : Optional [ str ] = None
logo : Optional [ str ] = None
tags : List [ str ] = [ ]
auth_schemes : List [ str ] = [ ]
2025-08-04 11:43:20 +08:00
categories : List [ str ] = [ ]
2025-08-02 16:22:17 +08:00
2025-08-09 01:08:50 +08:00
class AuthConfigField ( BaseModel ) :
name : str
displayName : str
type : str
description : Optional [ str ] = None
required : bool = False
default : Optional [ str ] = None
legacy_template_name : Optional [ str ] = None
class AuthConfigDetails ( BaseModel ) :
name : str
mode : str
fields : Dict [ str , Dict [ str , List [ AuthConfigField ] ] ]
class DetailedToolkitInfo ( BaseModel ) :
slug : str
name : str
description : Optional [ str ] = None
logo : Optional [ str ] = None
tags : List [ str ] = [ ]
auth_schemes : List [ str ] = [ ]
categories : List [ str ] = [ ]
auth_config_details : List [ AuthConfigDetails ] = [ ]
connected_account_initiation_fields : Optional [ Dict [ str , List [ AuthConfigField ] ] ] = None
base_url : Optional [ str ] = None
2025-08-10 05:12:16 +08:00
class ParameterSchema ( BaseModel ) :
properties : Dict [ str , Any ] = { }
required : Optional [ List [ str ] ] = None
class ToolInfo ( BaseModel ) :
slug : str
name : str
description : str
version : str
input_parameters : ParameterSchema = ParameterSchema ( )
output_parameters : ParameterSchema = ParameterSchema ( )
scopes : List [ str ] = [ ]
tags : List [ str ] = [ ]
no_auth : bool = False
class ToolsListResponse ( BaseModel ) :
items : List [ ToolInfo ]
next_cursor : Optional [ str ] = None
total_items : int
current_page : int = 1
total_pages : int = 1
2025-08-02 16:22:17 +08:00
class ToolkitService :
def __init__ ( self , api_key : Optional [ str ] = None ) :
self . client = ComposioClient . get_client ( api_key )
2025-08-04 11:43:20 +08:00
async def list_categories ( self ) - > List [ CategoryInfo ] :
2025-08-02 16:22:17 +08:00
try :
2025-08-04 11:43:20 +08:00
logger . info ( " Fetching Composio categories " )
popular_categories = [
{ " id " : " popular " , " name " : " Popular " } ,
{ " id " : " productivity " , " name " : " Productivity " } ,
{ " id " : " crm " , " name " : " CRM " } ,
{ " id " : " marketing " , " name " : " Marketing " } ,
{ " id " : " analytics " , " name " : " Analytics " } ,
{ " id " : " communication " , " name " : " Communication " } ,
{ " id " : " project-management " , " name " : " Project Management " } ,
{ " id " : " scheduling " , " name " : " Scheduling " } ,
]
2025-08-09 01:08:50 +08:00
special_apps = [
" googlesuper " ,
' googleclassroom ' ,
" docusign "
]
2025-08-04 11:43:20 +08:00
categories = [ CategoryInfo ( * * cat ) for cat in popular_categories ]
logger . info ( f " Successfully fetched { len ( categories ) } categories " )
return categories
2025-08-02 16:22:17 +08:00
2025-08-04 11:43:20 +08:00
except Exception as e :
logger . error ( f " Failed to list categories: { e } " , exc_info = True )
raise
2025-08-09 01:08:50 +08:00
async def list_toolkits ( self , limit : int = 500 , cursor : Optional [ str ] = None , category : Optional [ str ] = None ) - > Dict [ str , Any ] :
2025-08-04 11:43:20 +08:00
try :
2025-08-09 01:08:50 +08:00
logger . info ( f " Fetching toolkits with limit: { limit } , cursor: { cursor } , category: { category } " )
params = {
" limit " : limit ,
" managed_by " : " composio "
}
if cursor :
params [ " cursor " ] = cursor
2025-08-04 11:43:20 +08:00
if category :
2025-08-09 01:08:50 +08:00
params [ " category " ] = category
toolkits_response = self . client . toolkits . list ( * * params )
2025-08-02 16:22:17 +08:00
if hasattr ( toolkits_response , ' __dict__ ' ) :
2025-08-09 01:08:50 +08:00
response_data = toolkits_response . __dict__
else :
response_data = toolkits_response
items = response_data . get ( ' items ' , [ ] )
2025-08-02 16:22:17 +08:00
toolkits = [ ]
for item in items :
if hasattr ( item , ' __dict__ ' ) :
toolkit_data = item . __dict__
elif hasattr ( item , ' _asdict ' ) :
toolkit_data = item . _asdict ( )
else :
toolkit_data = item
auth_schemes = toolkit_data . get ( " auth_schemes " , [ ] )
2025-08-09 01:08:50 +08:00
composio_managed_auth_schemes = toolkit_data . get ( " composio_managed_auth_schemes " , [ ] )
2025-08-02 16:22:17 +08:00
2025-08-09 01:08:50 +08:00
if " OAUTH2 " not in auth_schemes or " OAUTH2 " not in composio_managed_auth_schemes :
2025-08-02 16:22:17 +08:00
continue
logo_url = None
meta = toolkit_data . get ( " meta " , { } )
if isinstance ( meta , dict ) :
logo_url = meta . get ( " logo " )
elif hasattr ( meta , ' __dict__ ' ) :
logo_url = meta . __dict__ . get ( " logo " )
if not logo_url :
logo_url = toolkit_data . get ( " logo " )
tags = [ ]
2025-08-04 11:43:20 +08:00
categories = [ ]
2025-08-02 16:22:17 +08:00
if isinstance ( meta , dict ) and " categories " in meta :
2025-08-04 11:43:20 +08:00
category_list = meta . get ( " categories " , [ ] )
for cat in category_list :
if isinstance ( cat , dict ) :
cat_name = cat . get ( " name " , " " )
cat_id = cat . get ( " id " , " " )
tags . append ( cat_name )
categories . append ( cat_id )
elif hasattr ( cat , ' __dict__ ' ) :
cat_name = cat . __dict__ . get ( " name " , " " )
cat_id = cat . __dict__ . get ( " id " , " " )
tags . append ( cat_name )
categories . append ( cat_id )
2025-08-02 16:22:17 +08:00
description = None
if isinstance ( meta , dict ) :
description = meta . get ( " description " )
elif hasattr ( meta , ' __dict__ ' ) :
description = meta . __dict__ . get ( " description " )
if not description :
description = toolkit_data . get ( " description " )
toolkit = ToolkitInfo (
slug = toolkit_data . get ( " slug " , " " ) ,
name = toolkit_data . get ( " name " , " " ) ,
description = description ,
logo = logo_url ,
tags = tags ,
2025-08-04 11:43:20 +08:00
auth_schemes = auth_schemes ,
categories = categories
2025-08-02 16:22:17 +08:00
)
toolkits . append ( toolkit )
2025-08-09 01:08:50 +08:00
result = {
" items " : toolkits ,
" total_items " : response_data . get ( " total_items " , len ( toolkits ) ) ,
" total_pages " : response_data . get ( " total_pages " , 1 ) ,
" current_page " : response_data . get ( " current_page " , 1 ) ,
" next_cursor " : response_data . get ( " next_cursor " )
}
logger . info ( f " Successfully fetched { len ( toolkits ) } toolkits with OAUTH2 in both auth schemes " + ( f " for category { category } " if category else " " ) )
return result
2025-08-02 16:22:17 +08:00
except Exception as e :
logger . error ( f " Failed to list toolkits: { e } " , exc_info = True )
raise
async def get_toolkit_by_slug ( self , slug : str ) - > Optional [ ToolkitInfo ] :
try :
2025-08-09 01:08:50 +08:00
toolkits_response = await self . list_toolkits ( )
toolkits = toolkits_response . get ( " items " , [ ] )
2025-08-02 16:22:17 +08:00
for toolkit in toolkits :
if toolkit . slug == slug :
return toolkit
return None
except Exception as e :
logger . error ( f " Failed to get toolkit { slug } : { e } " , exc_info = True )
raise
2025-08-09 01:08:50 +08:00
async def search_toolkits ( self , query : str , category : Optional [ str ] = None , limit : int = 100 , cursor : Optional [ str ] = None ) - > Dict [ str , Any ] :
2025-08-02 16:22:17 +08:00
try :
2025-08-09 01:08:50 +08:00
all_toolkits_response = await self . list_toolkits ( limit = 500 , cursor = cursor , category = category )
toolkits = all_toolkits_response . get ( " items " , [ ] )
2025-08-02 16:22:17 +08:00
query_lower = query . lower ( )
filtered_toolkits = [
toolkit for toolkit in toolkits
if query_lower in toolkit . name . lower ( )
or ( toolkit . description and query_lower in toolkit . description . lower ( ) )
or any ( query_lower in tag . lower ( ) for tag in toolkit . tags )
]
2025-08-09 01:08:50 +08:00
limited_results = filtered_toolkits [ : limit ]
result = {
" items " : limited_results ,
" total_items " : len ( filtered_toolkits ) ,
2025-08-09 14:11:47 +08:00
" total_pages " : 1 ,
2025-08-09 01:08:50 +08:00
" current_page " : 1 ,
2025-08-09 14:11:47 +08:00
" next_cursor " : None
2025-08-09 01:08:50 +08:00
}
logger . info ( f " Found { len ( filtered_toolkits ) } toolkits with OAUTH2 in both auth schemes matching query: { query } " + ( f " in category { category } " if category else " " ) )
return result
2025-08-02 16:22:17 +08:00
except Exception as e :
logger . error ( f " Failed to search toolkits: { e } " , exc_info = True )
2025-08-04 11:43:20 +08:00
raise
async def get_toolkit_icon ( self , toolkit_slug : str ) - > Optional [ str ] :
try :
logger . info ( f " Fetching toolkit icon for: { toolkit_slug } " )
toolkit_response = self . client . toolkits . retrieve ( toolkit_slug )
if hasattr ( toolkit_response , ' model_dump ' ) :
toolkit_dict = toolkit_response . model_dump ( )
elif hasattr ( toolkit_response , ' __dict__ ' ) :
toolkit_dict = toolkit_response . __dict__
else :
toolkit_dict = dict ( toolkit_response )
meta = toolkit_dict . get ( ' meta ' , { } )
if isinstance ( meta , dict ) :
logo = meta . get ( ' logo ' )
elif hasattr ( meta , ' __dict__ ' ) :
logo = meta . __dict__ . get ( ' logo ' )
else :
logo = None
logger . info ( f " Successfully fetched icon for { toolkit_slug } : { logo } " )
return logo
except Exception as e :
logger . error ( f " Failed to get toolkit icon for { toolkit_slug } : { e } " )
2025-08-09 01:08:50 +08:00
return None
async def get_detailed_toolkit_info ( self , toolkit_slug : str ) - > Optional [ DetailedToolkitInfo ] :
try :
logger . info ( f " Fetching detailed toolkit info for: { toolkit_slug } " )
toolkit_response = self . client . toolkits . retrieve ( toolkit_slug )
if hasattr ( toolkit_response , ' model_dump ' ) :
toolkit_dict = toolkit_response . model_dump ( )
elif hasattr ( toolkit_response , ' __dict__ ' ) :
toolkit_dict = toolkit_response . __dict__
else :
toolkit_dict = dict ( toolkit_response )
logger . info ( f " Raw toolkit response for { toolkit_slug } : { toolkit_response } " )
meta = toolkit_dict . get ( ' meta ' , { } )
if hasattr ( meta , ' __dict__ ' ) :
meta = meta . __dict__
detailed_toolkit = DetailedToolkitInfo (
slug = toolkit_dict . get ( ' slug ' , ' ' ) ,
name = toolkit_dict . get ( ' name ' , ' ' ) ,
description = meta . get ( ' description ' , ' ' ) if isinstance ( meta , dict ) else getattr ( meta , ' description ' , ' ' ) ,
logo = meta . get ( ' logo ' ) if isinstance ( meta , dict ) else getattr ( meta , ' logo ' , None ) ,
2025-08-09 14:11:47 +08:00
tags = [ ] ,
2025-08-09 01:08:50 +08:00
auth_schemes = toolkit_dict . get ( ' composio_managed_auth_schemes ' , [ ] ) ,
categories = [ ] ,
base_url = toolkit_dict . get ( ' base_url ' )
)
categories_data = meta . get ( ' categories ' , [ ] ) if isinstance ( meta , dict ) else getattr ( meta , ' categories ' , [ ] )
detailed_toolkit . categories = [
cat . get ( ' name ' , ' ' ) if isinstance ( cat , dict ) else getattr ( cat , ' name ' , ' ' )
for cat in categories_data
]
logger . info ( f " Parsed basic toolkit info: { detailed_toolkit } " )
auth_config_details = [ ]
raw_auth_configs = toolkit_dict . get ( ' auth_config_details ' , [ ] )
for config in raw_auth_configs :
if hasattr ( config , ' __dict__ ' ) :
config_dict = config . __dict__
else :
config_dict = config
fields_obj = config_dict . get ( ' fields ' )
if hasattr ( fields_obj , ' __dict__ ' ) :
fields_dict = fields_obj . __dict__
else :
fields_dict = fields_obj or { }
auth_fields = { }
for field_type , field_type_obj in fields_dict . items ( ) :
auth_fields [ field_type ] = { }
if hasattr ( field_type_obj , ' __dict__ ' ) :
field_type_dict = field_type_obj . __dict__
else :
field_type_dict = field_type_obj or { }
for requirement_level in [ ' required ' , ' optional ' ] :
field_list = field_type_dict . get ( requirement_level , [ ] )
auth_config_fields = [ ]
for field in field_list :
if hasattr ( field , ' __dict__ ' ) :
field_dict = field . __dict__
else :
field_dict = field
auth_config_fields . append ( AuthConfigField (
name = field_dict . get ( ' name ' , ' ' ) ,
2025-08-09 14:11:47 +08:00
displayName = field_dict . get ( ' display_name ' , ' ' ) ,
2025-08-09 01:08:50 +08:00
type = field_dict . get ( ' type ' , ' string ' ) ,
description = field_dict . get ( ' description ' ) ,
required = field_dict . get ( ' required ' , False ) ,
default = field_dict . get ( ' default ' ) ,
legacy_template_name = field_dict . get ( ' legacy_template_name ' )
) )
auth_fields [ field_type ] [ requirement_level ] = auth_config_fields
auth_config_details . append ( AuthConfigDetails (
name = config_dict . get ( ' name ' , ' ' ) ,
mode = config_dict . get ( ' mode ' , ' ' ) ,
fields = auth_fields
) )
detailed_toolkit . auth_config_details = auth_config_details
connected_account_initiation = None
for config in raw_auth_configs :
if hasattr ( config , ' __dict__ ' ) :
config_dict = config . __dict__
else :
config_dict = config
fields_obj = config_dict . get ( ' fields ' )
if hasattr ( fields_obj , ' __dict__ ' ) :
fields_dict = fields_obj . __dict__
else :
fields_dict = fields_obj or { }
initiation_obj = fields_dict . get ( ' connected_account_initiation ' )
if initiation_obj :
if hasattr ( initiation_obj , ' __dict__ ' ) :
initiation_dict = initiation_obj . __dict__
else :
initiation_dict = initiation_obj
connected_account_initiation = { }
for requirement_level in [ ' required ' , ' optional ' ] :
field_list = initiation_dict . get ( requirement_level , [ ] )
initiation_fields = [ ]
for field in field_list :
if hasattr ( field , ' __dict__ ' ) :
field_dict = field . __dict__
else :
field_dict = field
initiation_fields . append ( AuthConfigField (
name = field_dict . get ( ' name ' , ' ' ) ,
displayName = field_dict . get ( ' display_name ' , ' ' ) ,
type = field_dict . get ( ' type ' , ' string ' ) ,
description = field_dict . get ( ' description ' ) ,
required = field_dict . get ( ' required ' , False ) ,
default = field_dict . get ( ' default ' ) ,
legacy_template_name = field_dict . get ( ' legacy_template_name ' )
) )
connected_account_initiation [ requirement_level ] = initiation_fields
break
detailed_toolkit . connected_account_initiation_fields = connected_account_initiation
logger . info ( f " Successfully fetched detailed info for { toolkit_slug } " )
logger . info ( f " Initiation fields: { connected_account_initiation } " )
return detailed_toolkit
except Exception as e :
logger . error ( f " Failed to get detailed toolkit info for { toolkit_slug } : { e } " , exc_info = True )
2025-08-10 05:12:16 +08:00
return None
async def get_toolkit_tools ( self , toolkit_slug : str , limit : int = 50 , cursor : Optional [ str ] = None ) - > ToolsListResponse :
try :
logger . info ( f " Fetching tools for toolkit: { toolkit_slug } " )
params = {
" limit " : limit ,
" toolkit_slug " : toolkit_slug
}
if cursor :
params [ " cursor " ] = cursor
tools_response = self . client . tools . list ( * * params )
if hasattr ( tools_response , ' __dict__ ' ) :
response_data = tools_response . __dict__
else :
response_data = tools_response
items = response_data . get ( ' items ' , [ ] )
tools = [ ]
for item in items :
if hasattr ( item , ' __dict__ ' ) :
tool_data = item . __dict__
elif hasattr ( item , ' _asdict ' ) :
tool_data = item . _asdict ( )
else :
tool_data = item
input_params_raw = tool_data . get ( " input_parameters " , { } )
output_params_raw = tool_data . get ( " output_parameters " , { } )
input_parameters = ParameterSchema ( )
if isinstance ( input_params_raw , dict ) :
input_parameters . properties = input_params_raw . get ( " properties " , input_params_raw )
input_parameters . required = input_params_raw . get ( " required " )
output_parameters = ParameterSchema ( )
if isinstance ( output_params_raw , dict ) :
output_parameters . properties = output_params_raw . get ( " properties " , output_params_raw )
output_parameters . required = output_params_raw . get ( " required " )
tool = ToolInfo (
slug = tool_data . get ( " slug " , " " ) ,
name = tool_data . get ( " name " , " " ) ,
description = tool_data . get ( " description " , " " ) ,
version = tool_data . get ( " version " , " 1.0.0 " ) ,
input_parameters = input_parameters ,
output_parameters = output_parameters ,
scopes = tool_data . get ( " scopes " , [ ] ) ,
tags = tool_data . get ( " tags " , [ ] ) ,
no_auth = tool_data . get ( " no_auth " , False )
)
tools . append ( tool )
result = ToolsListResponse (
items = tools ,
total_items = response_data . get ( " total_items " , len ( tools ) ) ,
total_pages = response_data . get ( " total_pages " , 1 ) ,
current_page = response_data . get ( " current_page " , 1 ) ,
next_cursor = response_data . get ( " next_cursor " )
)
logger . info ( f " Successfully fetched { len ( tools ) } tools for toolkit { toolkit_slug } " )
return result
except Exception as e :
logger . error ( f " Failed to get tools for toolkit { toolkit_slug } : { e } " , exc_info = True )
return ToolsListResponse (
items = [ ] ,
total_items = 0 ,
current_page = 1 ,
total_pages = 1
)