Skip to content

Commit 4ed9ab7

Browse files
Merge pull request #1241 from escapade-mckv/ui-polishing
UI polishing
2 parents d295937 + f0efcd9 commit 4ed9ab7

File tree

14 files changed

+1063
-258
lines changed

14 files changed

+1063
-258
lines changed

backend/composio_integration/api.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
ComposioIntegrationService,
1313
ComposioIntegrationResult
1414
)
15-
from .toolkit_service import ToolkitInfo, ToolkitService, CategoryInfo
15+
from .toolkit_service import ToolkitInfo, ToolkitService, CategoryInfo, DetailedToolkitInfo
1616
from .composio_profile_service import ComposioProfileService, ComposioProfile
1717

1818
router = APIRouter(prefix="/composio", tags=["composio"])
@@ -49,6 +49,7 @@ class CreateProfileRequest(BaseModel):
4949
display_name: Optional[str] = None
5050
mcp_server_name: Optional[str] = None
5151
is_default: bool = False
52+
initiation_fields: Optional[Dict[str, str]] = None
5253

5354

5455
class ProfileResponse(BaseModel):
@@ -103,31 +104,62 @@ async def list_categories(
103104
@router.get("/toolkits")
104105
async def list_toolkits(
105106
limit: int = Query(100, le=500),
107+
cursor: Optional[str] = Query(None),
106108
search: Optional[str] = Query(None),
107109
category: Optional[str] = Query(None),
108110
user_id: str = Depends(get_current_user_id_from_jwt)
109111
) -> Dict[str, Any]:
110112
try:
111-
logger.info(f"Fetching Composio toolkits with limit: {limit}, search: {search}, category: {category}")
113+
logger.info(f"Fetching Composio toolkits with limit: {limit}, cursor: {cursor}, search: {search}, category: {category}")
112114

113115
service = get_integration_service()
114116

115117
if search:
116-
toolkits = await service.search_toolkits(search, category=category)
118+
result = await service.search_toolkits(search, category=category, limit=limit, cursor=cursor)
117119
else:
118-
toolkits = await service.list_available_toolkits(limit, category=category)
120+
result = await service.list_available_toolkits(limit, cursor=cursor, category=category)
119121

120122
return {
121123
"success": True,
122-
"toolkits": [toolkit.dict() for toolkit in toolkits],
123-
"total": len(toolkits)
124+
"toolkits": [toolkit.dict() for toolkit in result.get('items', [])],
125+
"total_items": result.get('total_items', 0),
126+
"total_pages": result.get('total_pages', 0),
127+
"current_page": result.get('current_page', 1),
128+
"next_cursor": result.get('next_cursor'),
129+
"has_more": result.get('next_cursor') is not None
124130
}
125131

126132
except Exception as e:
127133
logger.error(f"Failed to fetch toolkits: {e}", exc_info=True)
128134
raise HTTPException(status_code=500, detail=f"Failed to fetch toolkits: {str(e)}")
129135

130136

137+
@router.get("/toolkits/{toolkit_slug}/details")
138+
async def get_toolkit_details(
139+
toolkit_slug: str,
140+
user_id: str = Depends(get_current_user_id_from_jwt)
141+
) -> Dict[str, Any]:
142+
try:
143+
logger.info(f"Fetching detailed toolkit info for: {toolkit_slug}")
144+
145+
toolkit_service = ToolkitService()
146+
detailed_toolkit = await toolkit_service.get_detailed_toolkit_info(toolkit_slug)
147+
148+
if not detailed_toolkit:
149+
raise HTTPException(status_code=404, detail=f"Toolkit {toolkit_slug} not found")
150+
151+
return {
152+
"success": True,
153+
"toolkit": detailed_toolkit.dict()
154+
}
155+
156+
except HTTPException:
157+
raise
158+
except Exception as e:
159+
logger.error(f"Failed to fetch toolkit details for {toolkit_slug}: {e}", exc_info=True)
160+
raise HTTPException(status_code=500, detail=f"Failed to fetch toolkit details: {str(e)}")
161+
162+
131163
@router.post("/integrate", response_model=IntegrationStatusResponse)
132164
async def integrate_toolkit(
133165
request: IntegrateToolkitRequest,
@@ -182,7 +214,8 @@ async def create_profile(
182214
profile_name=request.profile_name,
183215
display_name=request.display_name,
184216
mcp_server_name=request.mcp_server_name,
185-
save_as_profile=True
217+
save_as_profile=True,
218+
initiation_fields=request.initiation_fields
186219
)
187220

188221
logger.info(f"Integration result for {request.toolkit_slug}: redirect_url = {result.connected_account.redirect_url}")

backend/composio_integration/auth_config_service.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional, List
1+
from typing import Optional, List, Dict, Any, Union
22
from composio_client import Composio
33
from utils.logger import logger
44
from pydantic import BaseModel
@@ -18,15 +18,51 @@ class AuthConfigService:
1818
def __init__(self, api_key: Optional[str] = None):
1919
self.client = ComposioClient.get_client(api_key)
2020

21-
async def create_auth_config(self, toolkit_slug: str) -> AuthConfig:
21+
def _convert_field_value(self, value: str, field_type: str) -> Union[str, bool, float]:
22+
if field_type == 'boolean':
23+
if isinstance(value, bool):
24+
return value
25+
return value.lower() in ('true', '1', 'yes', 'on')
26+
elif field_type == 'number' or field_type == 'double':
27+
try:
28+
return float(value)
29+
except (ValueError, TypeError):
30+
logger.warning(f"Failed to convert '{value}' to float, using as string")
31+
return str(value)
32+
else:
33+
return str(value)
34+
35+
async def create_auth_config(
36+
self,
37+
toolkit_slug: str,
38+
initiation_fields: Optional[Dict[str, str]] = None
39+
) -> AuthConfig:
2240
try:
2341
logger.info(f"Creating auth config for toolkit: {toolkit_slug}")
42+
logger.info(f"Initiation fields: {initiation_fields}")
43+
44+
credentials = {"region": "ind"}
45+
46+
if initiation_fields:
47+
for field_name, field_value in initiation_fields.items():
48+
if field_value:
49+
if field_name == "suffix.one":
50+
credentials["extension"] = str(field_value)
51+
else:
52+
credentials[field_name] = str(field_value)
53+
54+
logger.info(f"Using credentials: {credentials}")
2455

2556
response = self.client.auth_configs.create(
26-
toolkit={"slug": toolkit_slug}
57+
toolkit={
58+
"slug": toolkit_slug
59+
},
60+
auth_config={
61+
"type": "use_composio_managed_auth",
62+
"credentials": credentials
63+
}
2764
)
2865

29-
# Access Pydantic model attributes directly
3066
auth_config_obj = response.auth_config
3167

3268
auth_config = AuthConfig(
@@ -53,7 +89,6 @@ async def get_auth_config(self, auth_config_id: str) -> Optional[AuthConfig]:
5389
if not response:
5490
return None
5591

56-
# Access Pydantic model attributes directly
5792
return AuthConfig(
5893
id=response.id,
5994
auth_scheme=response.auth_scheme,

backend/composio_integration/composio_service.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,23 +40,29 @@ async def integrate_toolkit(
4040
profile_name: Optional[str] = None,
4141
display_name: Optional[str] = None,
4242
mcp_server_name: Optional[str] = None,
43-
save_as_profile: bool = True
43+
save_as_profile: bool = True,
44+
initiation_fields: Optional[Dict[str, str]] = None
4445
) -> ComposioIntegrationResult:
4546
try:
4647
logger.info(f"Starting Composio integration for toolkit: {toolkit_slug}")
48+
logger.info(f"Initiation fields: {initiation_fields}")
4749

4850
toolkit = await self.toolkit_service.get_toolkit_by_slug(toolkit_slug)
4951
if not toolkit:
5052
raise ValueError(f"Toolkit '{toolkit_slug}' not found")
5153

5254
logger.info(f"Step 1 complete: Verified toolkit {toolkit_slug}")
5355

54-
auth_config = await self.auth_config_service.create_auth_config(toolkit_slug)
56+
auth_config = await self.auth_config_service.create_auth_config(
57+
toolkit_slug,
58+
initiation_fields=initiation_fields
59+
)
5560
logger.info(f"Step 2 complete: Created auth config {auth_config.id}")
5661

5762
connected_account = await self.connected_account_service.create_connected_account(
5863
auth_config_id=auth_config.id,
59-
user_id=user_id
64+
user_id=user_id,
65+
initiation_fields=initiation_fields
6066
)
6167
logger.info(f"Step 3 complete: Connected account {connected_account.id}")
6268

@@ -113,11 +119,11 @@ async def integrate_toolkit(
113119
logger.error(f"Failed to integrate toolkit {toolkit_slug}: {e}", exc_info=True)
114120
raise
115121

116-
async def list_available_toolkits(self, limit: int = 100, category: Optional[str] = None) -> List[ToolkitInfo]:
117-
return await self.toolkit_service.list_toolkits(limit=limit, category=category)
122+
async def list_available_toolkits(self, limit: int = 100, cursor: Optional[str] = None, category: Optional[str] = None) -> Dict[str, Any]:
123+
return await self.toolkit_service.list_toolkits(limit=limit, cursor=cursor, category=category)
118124

119-
async def search_toolkits(self, query: str, category: Optional[str] = None) -> List[ToolkitInfo]:
120-
return await self.toolkit_service.search_toolkits(query, category=category)
125+
async def search_toolkits(self, query: str, category: Optional[str] = None, limit: int = 100, cursor: Optional[str] = None) -> Dict[str, Any]:
126+
return await self.toolkit_service.search_toolkits(query, category=category, limit=limit, cursor=cursor)
121127

122128
async def get_integration_status(self, connected_account_id: str) -> Dict[str, Any]:
123129
return await self.connected_account_service.get_auth_status(connected_account_id)

backend/composio_integration/connected_account_service.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,34 @@ def _extract_val_dict(self, val_obj) -> Dict[str, Any]:
5858
async def create_connected_account(
5959
self,
6060
auth_config_id: str,
61-
user_id: str
61+
user_id: str,
62+
initiation_fields: Optional[Dict[str, str]] = None
6263
) -> ConnectedAccount:
6364
try:
6465
logger.info(f"Creating connected account for auth_config: {auth_config_id}, user: {user_id}")
66+
logger.info(f"Initiation fields for connected account: {initiation_fields}")
67+
68+
state_val = {"status": "INITIALIZING"}
69+
70+
if initiation_fields:
71+
for field_name, field_value in initiation_fields.items():
72+
if field_value:
73+
if field_name == "suffix.one":
74+
state_val["extension"] = str(field_value)
75+
else:
76+
state_val[field_name] = str(field_value)
77+
78+
logger.info(f"Using state.val: {state_val}")
6579

6680
response = self.client.connected_accounts.create(
67-
auth_config={"id": auth_config_id},
81+
auth_config={
82+
"id": auth_config_id
83+
},
6884
connection={
6985
"user_id": user_id,
7086
"state": {
7187
"authScheme": "OAUTH2",
72-
"val": {
73-
"status": "INITIALIZING"
74-
}
88+
"val": state_val,
7589
}
7690
}
7791
)

0 commit comments

Comments
 (0)