mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-04 21:32:39 +02:00
fix: fixed composio issues
This commit is contained in:
parent
47b2994ec7
commit
cea8618aed
25 changed files with 1756 additions and 461 deletions
|
|
@ -0,0 +1,41 @@
|
|||
from typing import Any
|
||||
|
||||
from app.db import SearchSourceConnector
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
|
||||
def split_recipients(value: str | None) -> list[str]:
|
||||
if not value:
|
||||
return []
|
||||
return [recipient.strip() for recipient in value.split(",") if recipient.strip()]
|
||||
|
||||
|
||||
def unwrap_composio_data(data: Any) -> Any:
|
||||
if isinstance(data, dict):
|
||||
inner = data.get("data", data)
|
||||
if isinstance(inner, dict):
|
||||
return inner.get("response_data", inner)
|
||||
return inner
|
||||
return data
|
||||
|
||||
|
||||
async def execute_composio_gmail_tool(
|
||||
connector: SearchSourceConnector,
|
||||
user_id: str,
|
||||
tool_name: str,
|
||||
params: dict[str, Any],
|
||||
) -> tuple[Any, str | None]:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if not cca_id:
|
||||
return None, "Composio connected account ID not found for this Gmail connector."
|
||||
|
||||
result = await ComposioService().execute_tool(
|
||||
connected_account_id=cca_id,
|
||||
tool_name=tool_name,
|
||||
params=params,
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
)
|
||||
if not result.get("success"):
|
||||
return None, result.get("error", "Unknown Composio Gmail error")
|
||||
|
||||
return unwrap_composio_data(result.get("data")), None
|
||||
|
|
@ -157,16 +157,13 @@ def create_create_gmail_draft_tool(
|
|||
f"Creating Gmail draft: to='{final_to}', subject='{final_subject}', connector={actual_connector_id}"
|
||||
)
|
||||
|
||||
if (
|
||||
is_composio_gmail = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_gmail:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
creds = build_composio_credentials(cca_id)
|
||||
else:
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this Gmail connector.",
|
||||
|
|
@ -208,10 +205,6 @@ def create_create_gmail_draft_tool(
|
|||
expiry=datetime.fromisoformat(exp) if exp else None,
|
||||
)
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
|
||||
message = MIMEText(final_body)
|
||||
message["to"] = final_to
|
||||
message["subject"] = final_subject
|
||||
|
|
@ -222,15 +215,43 @@ def create_create_gmail_draft_tool(
|
|||
raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
||||
|
||||
try:
|
||||
created = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
gmail_service.users()
|
||||
.drafts()
|
||||
.create(userId="me", body={"message": {"raw": raw}})
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
if is_composio_gmail:
|
||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
||||
execute_composio_gmail_tool,
|
||||
split_recipients,
|
||||
)
|
||||
|
||||
created, error = await execute_composio_gmail_tool(
|
||||
connector,
|
||||
user_id,
|
||||
"GMAIL_CREATE_EMAIL_DRAFT",
|
||||
{
|
||||
"user_id": "me",
|
||||
"recipient_email": final_to,
|
||||
"subject": final_subject,
|
||||
"body": final_body,
|
||||
"cc": split_recipients(final_cc),
|
||||
"bcc": split_recipients(final_bcc),
|
||||
"is_html": False,
|
||||
},
|
||||
)
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
if not isinstance(created, dict):
|
||||
created = {}
|
||||
else:
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
created = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
gmail_service.users()
|
||||
.drafts()
|
||||
.create(userId="me", body={"message": {"raw": raw}})
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
except Exception as api_err:
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,54 @@ def create_read_gmail_email_tool(
|
|||
"message": "No Gmail connector found. Please connect Gmail in your workspace settings.",
|
||||
}
|
||||
|
||||
if (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
|
||||
):
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found.",
|
||||
}
|
||||
|
||||
from app.agents.new_chat.tools.gmail.search_emails import (
|
||||
_format_gmail_summary,
|
||||
)
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
service = ComposioService()
|
||||
detail, error = await service.get_gmail_message_detail(
|
||||
connected_account_id=cca_id,
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
message_id=message_id,
|
||||
)
|
||||
if error:
|
||||
return {"status": "error", "message": error}
|
||||
if not detail:
|
||||
return {
|
||||
"status": "not_found",
|
||||
"message": f"Email with ID '{message_id}' not found.",
|
||||
}
|
||||
|
||||
summary = _format_gmail_summary(detail)
|
||||
content = (
|
||||
f"# {summary['subject']}\n\n"
|
||||
f"**From:** {summary['from']}\n"
|
||||
f"**To:** {summary['to']}\n"
|
||||
f"**Date:** {summary['date']}\n\n"
|
||||
f"## Message Content\n\n"
|
||||
f"{detail.get('messageText') or detail.get('snippet') or ''}\n\n"
|
||||
f"## Message Details\n\n"
|
||||
f"- **Message ID:** {summary['message_id']}\n"
|
||||
f"- **Thread ID:** {summary['thread_id']}\n"
|
||||
)
|
||||
return {
|
||||
"status": "success",
|
||||
"message_id": summary["message_id"] or message_id,
|
||||
"content": content,
|
||||
}
|
||||
|
||||
from app.agents.new_chat.tools.gmail.search_emails import _build_credentials
|
||||
|
||||
creds = _build_credentials(connector)
|
||||
|
|
|
|||
|
|
@ -39,12 +39,7 @@ def _build_credentials(connector: SearchSourceConnector):
|
|||
from app.utils.google_credentials import COMPOSIO_GOOGLE_CONNECTOR_TYPES
|
||||
|
||||
if connector.connector_type in COMPOSIO_GOOGLE_CONNECTOR_TYPES:
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if not cca_id:
|
||||
raise ValueError("Composio connected account ID not found.")
|
||||
return build_composio_credentials(cca_id)
|
||||
raise ValueError("Composio connectors must use Composio tool execution.")
|
||||
|
||||
from google.oauth2.credentials import Credentials
|
||||
|
||||
|
|
@ -67,6 +62,63 @@ def _build_credentials(connector: SearchSourceConnector):
|
|||
)
|
||||
|
||||
|
||||
def _gmail_headers(message: dict[str, Any]) -> dict[str, str]:
|
||||
headers = message.get("payload", {}).get("headers", [])
|
||||
return {
|
||||
header.get("name", "").lower(): header.get("value", "")
|
||||
for header in headers
|
||||
if isinstance(header, dict)
|
||||
}
|
||||
|
||||
|
||||
def _format_gmail_summary(message: dict[str, Any]) -> dict[str, Any]:
|
||||
headers = _gmail_headers(message)
|
||||
return {
|
||||
"message_id": message.get("id") or message.get("messageId"),
|
||||
"thread_id": message.get("threadId"),
|
||||
"subject": message.get("subject") or headers.get("subject", "No Subject"),
|
||||
"from": message.get("sender") or headers.get("from", "Unknown"),
|
||||
"to": message.get("to") or headers.get("to", ""),
|
||||
"date": message.get("messageTimestamp") or headers.get("date", ""),
|
||||
"snippet": message.get("snippet") or message.get("messageText", "")[:300],
|
||||
"labels": message.get("labelIds", []),
|
||||
}
|
||||
|
||||
|
||||
async def _search_composio_gmail(
|
||||
connector: SearchSourceConnector,
|
||||
user_id: str,
|
||||
query: str,
|
||||
max_results: int,
|
||||
) -> dict[str, Any]:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found.",
|
||||
}
|
||||
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
service = ComposioService()
|
||||
messages, _next_token, _estimate, error = await service.get_gmail_messages(
|
||||
connected_account_id=cca_id,
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
query=query,
|
||||
max_results=max_results,
|
||||
)
|
||||
if error:
|
||||
return {"status": "error", "message": error}
|
||||
|
||||
emails = [_format_gmail_summary(message) for message in messages]
|
||||
return {
|
||||
"status": "success",
|
||||
"emails": emails,
|
||||
"total": len(emails),
|
||||
"message": "No emails found." if not emails else None,
|
||||
}
|
||||
|
||||
|
||||
def create_search_gmail_tool(
|
||||
db_session: AsyncSession | None = None,
|
||||
search_space_id: int | None = None,
|
||||
|
|
@ -110,6 +162,14 @@ def create_search_gmail_tool(
|
|||
"message": "No Gmail connector found. Please connect Gmail in your workspace settings.",
|
||||
}
|
||||
|
||||
if (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
|
||||
):
|
||||
return await _search_composio_gmail(
|
||||
connector, str(user_id), query, max_results
|
||||
)
|
||||
|
||||
creds = _build_credentials(connector)
|
||||
|
||||
from app.connectors.google_gmail_connector import GoogleGmailConnector
|
||||
|
|
|
|||
|
|
@ -158,16 +158,13 @@ def create_send_gmail_email_tool(
|
|||
f"Sending Gmail email: to='{final_to}', subject='{final_subject}', connector={actual_connector_id}"
|
||||
)
|
||||
|
||||
if (
|
||||
is_composio_gmail = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_gmail:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
creds = build_composio_credentials(cca_id)
|
||||
else:
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this Gmail connector.",
|
||||
|
|
@ -209,10 +206,6 @@ def create_send_gmail_email_tool(
|
|||
expiry=datetime.fromisoformat(exp) if exp else None,
|
||||
)
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
|
||||
message = MIMEText(final_body)
|
||||
message["to"] = final_to
|
||||
message["subject"] = final_subject
|
||||
|
|
@ -223,15 +216,43 @@ def create_send_gmail_email_tool(
|
|||
raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
||||
|
||||
try:
|
||||
sent = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
gmail_service.users()
|
||||
.messages()
|
||||
.send(userId="me", body={"raw": raw})
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
if is_composio_gmail:
|
||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
||||
execute_composio_gmail_tool,
|
||||
split_recipients,
|
||||
)
|
||||
|
||||
sent, error = await execute_composio_gmail_tool(
|
||||
connector,
|
||||
user_id,
|
||||
"GMAIL_SEND_EMAIL",
|
||||
{
|
||||
"user_id": "me",
|
||||
"recipient_email": final_to,
|
||||
"subject": final_subject,
|
||||
"body": final_body,
|
||||
"cc": split_recipients(final_cc),
|
||||
"bcc": split_recipients(final_bcc),
|
||||
"is_html": False,
|
||||
},
|
||||
)
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
if not isinstance(sent, dict):
|
||||
sent = {}
|
||||
else:
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
sent = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
gmail_service.users()
|
||||
.messages()
|
||||
.send(userId="me", body={"raw": raw})
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
except Exception as api_err:
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
|
|
|
|||
|
|
@ -158,16 +158,13 @@ def create_trash_gmail_email_tool(
|
|||
f"Trashing Gmail email: message_id='{final_message_id}', connector={final_connector_id}"
|
||||
)
|
||||
|
||||
if (
|
||||
is_composio_gmail = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_gmail:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
creds = build_composio_credentials(cca_id)
|
||||
else:
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this Gmail connector.",
|
||||
|
|
@ -209,20 +206,33 @@ def create_trash_gmail_email_tool(
|
|||
expiry=datetime.fromisoformat(exp) if exp else None,
|
||||
)
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
|
||||
try:
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
gmail_service.users()
|
||||
.messages()
|
||||
.trash(userId="me", id=final_message_id)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
if is_composio_gmail:
|
||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
||||
execute_composio_gmail_tool,
|
||||
)
|
||||
|
||||
_trashed, error = await execute_composio_gmail_tool(
|
||||
connector,
|
||||
user_id,
|
||||
"GMAIL_MOVE_TO_TRASH",
|
||||
{"user_id": "me", "message_id": final_message_id},
|
||||
)
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
else:
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
gmail_service.users()
|
||||
.messages()
|
||||
.trash(userId="me", id=final_message_id)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
except Exception as api_err:
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
|
|
|
|||
|
|
@ -188,16 +188,13 @@ def create_update_gmail_draft_tool(
|
|||
f"Updating Gmail draft: subject='{final_subject}', connector={final_connector_id}"
|
||||
)
|
||||
|
||||
if (
|
||||
is_composio_gmail = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_gmail:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
creds = build_composio_credentials(cca_id)
|
||||
else:
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this Gmail connector.",
|
||||
|
|
@ -239,18 +236,22 @@ def create_update_gmail_draft_tool(
|
|||
expiry=datetime.fromisoformat(exp) if exp else None,
|
||||
)
|
||||
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
|
||||
# Resolve draft_id if not already available
|
||||
if not final_draft_id:
|
||||
logger.info(
|
||||
f"draft_id not in metadata, looking up via drafts.list for message_id={message_id}"
|
||||
)
|
||||
final_draft_id = await _find_draft_id_by_message(
|
||||
gmail_service, message_id
|
||||
)
|
||||
if is_composio_gmail:
|
||||
final_draft_id = await _find_composio_draft_id_by_message(
|
||||
connector, user_id, message_id
|
||||
)
|
||||
else:
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
final_draft_id = await _find_draft_id_by_message(
|
||||
gmail_service, message_id
|
||||
)
|
||||
|
||||
if not final_draft_id:
|
||||
return {
|
||||
|
|
@ -272,19 +273,48 @@ def create_update_gmail_draft_tool(
|
|||
raw = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
||||
|
||||
try:
|
||||
updated = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
gmail_service.users()
|
||||
.drafts()
|
||||
.update(
|
||||
userId="me",
|
||||
id=final_draft_id,
|
||||
body={"message": {"raw": raw}},
|
||||
)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
if is_composio_gmail:
|
||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
||||
execute_composio_gmail_tool,
|
||||
split_recipients,
|
||||
)
|
||||
|
||||
updated, error = await execute_composio_gmail_tool(
|
||||
connector,
|
||||
user_id,
|
||||
"GMAIL_UPDATE_DRAFT",
|
||||
{
|
||||
"user_id": "me",
|
||||
"draft_id": final_draft_id,
|
||||
"recipient_email": final_to,
|
||||
"subject": final_subject,
|
||||
"body": final_body,
|
||||
"cc": split_recipients(final_cc),
|
||||
"bcc": split_recipients(final_bcc),
|
||||
"is_html": False,
|
||||
},
|
||||
)
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
if not isinstance(updated, dict):
|
||||
updated = {}
|
||||
else:
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
gmail_service = build("gmail", "v1", credentials=creds)
|
||||
updated = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
gmail_service.users()
|
||||
.drafts()
|
||||
.update(
|
||||
userId="me",
|
||||
id=final_draft_id,
|
||||
body={"message": {"raw": raw}},
|
||||
)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
except Exception as api_err:
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
|
|
@ -408,3 +438,35 @@ async def _find_draft_id_by_message(gmail_service: Any, message_id: str) -> str
|
|||
except Exception as e:
|
||||
logger.warning(f"Failed to look up draft by message_id: {e}")
|
||||
return None
|
||||
|
||||
|
||||
async def _find_composio_draft_id_by_message(
|
||||
connector: Any, user_id: str, message_id: str
|
||||
) -> str | None:
|
||||
from app.agents.new_chat.tools.gmail.composio_helpers import (
|
||||
execute_composio_gmail_tool,
|
||||
)
|
||||
|
||||
page_token = ""
|
||||
while True:
|
||||
params: dict[str, Any] = {
|
||||
"user_id": "me",
|
||||
"max_results": 100,
|
||||
"verbose": False,
|
||||
}
|
||||
if page_token:
|
||||
params["page_token"] = page_token
|
||||
|
||||
data, error = await execute_composio_gmail_tool(
|
||||
connector, user_id, "GMAIL_LIST_DRAFTS", params
|
||||
)
|
||||
if error or not isinstance(data, dict):
|
||||
return None
|
||||
|
||||
for draft in data.get("drafts", []):
|
||||
if draft.get("message", {}).get("id") == message_id:
|
||||
return draft.get("id")
|
||||
|
||||
page_token = data.get("nextPageToken") or data.get("next_page_token") or ""
|
||||
if not page_token:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -168,16 +168,13 @@ def create_create_calendar_event_tool(
|
|||
f"Creating calendar event: summary='{final_summary}', connector={actual_connector_id}"
|
||||
)
|
||||
|
||||
if (
|
||||
is_composio_calendar = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_calendar:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
creds = build_composio_credentials(cca_id)
|
||||
else:
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this connector.",
|
||||
|
|
@ -211,10 +208,6 @@ def create_create_calendar_event_tool(
|
|||
expiry=datetime.fromisoformat(exp) if exp else None,
|
||||
)
|
||||
|
||||
service = await asyncio.get_event_loop().run_in_executor(
|
||||
None, lambda: build("calendar", "v3", credentials=creds)
|
||||
)
|
||||
|
||||
tz = context.get("timezone", "UTC")
|
||||
event_body: dict[str, Any] = {
|
||||
"summary": final_summary,
|
||||
|
|
@ -231,14 +224,51 @@ def create_create_calendar_event_tool(
|
|||
]
|
||||
|
||||
try:
|
||||
created = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
service.events()
|
||||
.insert(calendarId="primary", body=event_body)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
if is_composio_calendar:
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
composio_params = {
|
||||
"calendar_id": "primary",
|
||||
"summary": final_summary,
|
||||
"start_datetime": final_start_datetime,
|
||||
"end_datetime": final_end_datetime,
|
||||
"timezone": tz,
|
||||
"attendees": final_attendees or [],
|
||||
}
|
||||
if final_description:
|
||||
composio_params["description"] = final_description
|
||||
if final_location:
|
||||
composio_params["location"] = final_location
|
||||
|
||||
composio_result = await ComposioService().execute_tool(
|
||||
connected_account_id=cca_id,
|
||||
tool_name="GOOGLECALENDAR_CREATE_EVENT",
|
||||
params=composio_params,
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
)
|
||||
if not composio_result.get("success"):
|
||||
raise RuntimeError(
|
||||
composio_result.get(
|
||||
"error", "Unknown Composio Calendar error"
|
||||
)
|
||||
)
|
||||
created = composio_result.get("data", {})
|
||||
if isinstance(created, dict):
|
||||
created = created.get("data", created)
|
||||
if isinstance(created, dict):
|
||||
created = created.get("response_data", created)
|
||||
else:
|
||||
service = await asyncio.get_event_loop().run_in_executor(
|
||||
None, lambda: build("calendar", "v3", credentials=creds)
|
||||
)
|
||||
created = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
service.events()
|
||||
.insert(calendarId="primary", body=event_body)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
except Exception as api_err:
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
|
|
|
|||
|
|
@ -159,16 +159,13 @@ def create_delete_calendar_event_tool(
|
|||
f"Deleting calendar event: event_id='{final_event_id}', connector={actual_connector_id}"
|
||||
)
|
||||
|
||||
if (
|
||||
is_composio_calendar = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_calendar:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
creds = build_composio_credentials(cca_id)
|
||||
else:
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this connector.",
|
||||
|
|
@ -202,19 +199,34 @@ def create_delete_calendar_event_tool(
|
|||
expiry=datetime.fromisoformat(exp) if exp else None,
|
||||
)
|
||||
|
||||
service = await asyncio.get_event_loop().run_in_executor(
|
||||
None, lambda: build("calendar", "v3", credentials=creds)
|
||||
)
|
||||
|
||||
try:
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
service.events()
|
||||
.delete(calendarId="primary", eventId=final_event_id)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
if is_composio_calendar:
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
composio_result = await ComposioService().execute_tool(
|
||||
connected_account_id=cca_id,
|
||||
tool_name="GOOGLECALENDAR_DELETE_EVENT",
|
||||
params={"calendar_id": "primary", "event_id": final_event_id},
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
)
|
||||
if not composio_result.get("success"):
|
||||
raise RuntimeError(
|
||||
composio_result.get(
|
||||
"error", "Unknown Composio Calendar error"
|
||||
)
|
||||
)
|
||||
else:
|
||||
service = await asyncio.get_event_loop().run_in_executor(
|
||||
None, lambda: build("calendar", "v3", credentials=creds)
|
||||
)
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
service.events()
|
||||
.delete(calendarId="primary", eventId=final_event_id)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
except Exception as api_err:
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,35 @@ _CALENDAR_TYPES = [
|
|||
]
|
||||
|
||||
|
||||
def _to_calendar_boundary(value: str, *, is_end: bool) -> str:
|
||||
if "T" in value:
|
||||
return value
|
||||
time = "23:59:59" if is_end else "00:00:00"
|
||||
return f"{value}T{time}Z"
|
||||
|
||||
|
||||
def _format_calendar_events(events_raw: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
events = []
|
||||
for ev in events_raw:
|
||||
start = ev.get("start", {})
|
||||
end = ev.get("end", {})
|
||||
attendees_raw = ev.get("attendees", [])
|
||||
events.append(
|
||||
{
|
||||
"event_id": ev.get("id"),
|
||||
"summary": ev.get("summary", "No Title"),
|
||||
"start": start.get("dateTime") or start.get("date", ""),
|
||||
"end": end.get("dateTime") or end.get("date", ""),
|
||||
"location": ev.get("location", ""),
|
||||
"description": ev.get("description", ""),
|
||||
"html_link": ev.get("htmlLink", ""),
|
||||
"attendees": [a.get("email", "") for a in attendees_raw[:10]],
|
||||
"status": ev.get("status", ""),
|
||||
}
|
||||
)
|
||||
return events
|
||||
|
||||
|
||||
def create_search_calendar_events_tool(
|
||||
db_session: AsyncSession | None = None,
|
||||
search_space_id: int | None = None,
|
||||
|
|
@ -61,22 +90,47 @@ def create_search_calendar_events_tool(
|
|||
"message": "No Google Calendar connector found. Please connect Google Calendar in your workspace settings.",
|
||||
}
|
||||
|
||||
creds = _build_credentials(connector)
|
||||
if (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
|
||||
):
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this connector.",
|
||||
}
|
||||
|
||||
from app.connectors.google_calendar_connector import GoogleCalendarConnector
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
cal = GoogleCalendarConnector(
|
||||
credentials=creds,
|
||||
session=db_session,
|
||||
user_id=user_id,
|
||||
connector_id=connector.id,
|
||||
)
|
||||
events_raw, error = await ComposioService().get_calendar_events(
|
||||
connected_account_id=cca_id,
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
time_min=_to_calendar_boundary(start_date, is_end=False),
|
||||
time_max=_to_calendar_boundary(end_date, is_end=True),
|
||||
max_results=max_results,
|
||||
)
|
||||
if not events_raw and not error:
|
||||
error = "No events found in the specified date range."
|
||||
else:
|
||||
creds = _build_credentials(connector)
|
||||
|
||||
events_raw, error = await cal.get_all_primary_calendar_events(
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
max_results=max_results,
|
||||
)
|
||||
from app.connectors.google_calendar_connector import (
|
||||
GoogleCalendarConnector,
|
||||
)
|
||||
|
||||
cal = GoogleCalendarConnector(
|
||||
credentials=creds,
|
||||
session=db_session,
|
||||
user_id=user_id,
|
||||
connector_id=connector.id,
|
||||
)
|
||||
|
||||
events_raw, error = await cal.get_all_primary_calendar_events(
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
max_results=max_results,
|
||||
)
|
||||
|
||||
if error:
|
||||
if (
|
||||
|
|
@ -97,24 +151,7 @@ def create_search_calendar_events_tool(
|
|||
}
|
||||
return {"status": "error", "message": error}
|
||||
|
||||
events = []
|
||||
for ev in events_raw:
|
||||
start = ev.get("start", {})
|
||||
end = ev.get("end", {})
|
||||
attendees_raw = ev.get("attendees", [])
|
||||
events.append(
|
||||
{
|
||||
"event_id": ev.get("id"),
|
||||
"summary": ev.get("summary", "No Title"),
|
||||
"start": start.get("dateTime") or start.get("date", ""),
|
||||
"end": end.get("dateTime") or end.get("date", ""),
|
||||
"location": ev.get("location", ""),
|
||||
"description": ev.get("description", ""),
|
||||
"html_link": ev.get("htmlLink", ""),
|
||||
"attendees": [a.get("email", "") for a in attendees_raw[:10]],
|
||||
"status": ev.get("status", ""),
|
||||
}
|
||||
)
|
||||
events = _format_calendar_events(events_raw)
|
||||
|
||||
return {"status": "success", "events": events, "total": len(events)}
|
||||
|
||||
|
|
|
|||
|
|
@ -192,16 +192,13 @@ def create_update_calendar_event_tool(
|
|||
f"Updating calendar event: event_id='{final_event_id}', connector={actual_connector_id}"
|
||||
)
|
||||
|
||||
if (
|
||||
is_composio_calendar = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_calendar:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
creds = build_composio_credentials(cca_id)
|
||||
else:
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this connector.",
|
||||
|
|
@ -235,10 +232,6 @@ def create_update_calendar_event_tool(
|
|||
expiry=datetime.fromisoformat(exp) if exp else None,
|
||||
)
|
||||
|
||||
service = await asyncio.get_event_loop().run_in_executor(
|
||||
None, lambda: build("calendar", "v3", credentials=creds)
|
||||
)
|
||||
|
||||
update_body: dict[str, Any] = {}
|
||||
if final_new_summary is not None:
|
||||
update_body["summary"] = final_new_summary
|
||||
|
|
@ -264,18 +257,65 @@ def create_update_calendar_event_tool(
|
|||
}
|
||||
|
||||
try:
|
||||
updated = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
service.events()
|
||||
.patch(
|
||||
calendarId="primary",
|
||||
eventId=final_event_id,
|
||||
body=update_body,
|
||||
if is_composio_calendar:
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
composio_params: dict[str, Any] = {
|
||||
"calendar_id": "primary",
|
||||
"event_id": final_event_id,
|
||||
}
|
||||
if final_new_summary is not None:
|
||||
composio_params["summary"] = final_new_summary
|
||||
if final_new_start_datetime is not None:
|
||||
composio_params["start_time"] = final_new_start_datetime
|
||||
if final_new_end_datetime is not None:
|
||||
composio_params["end_time"] = final_new_end_datetime
|
||||
if final_new_description is not None:
|
||||
composio_params["description"] = final_new_description
|
||||
if final_new_location is not None:
|
||||
composio_params["location"] = final_new_location
|
||||
if final_new_attendees is not None:
|
||||
composio_params["attendees"] = [
|
||||
e.strip() for e in final_new_attendees if e.strip()
|
||||
]
|
||||
if not _is_date_only(
|
||||
final_new_start_datetime or final_new_end_datetime or ""
|
||||
):
|
||||
composio_params["timezone"] = context.get("timezone", "UTC")
|
||||
|
||||
composio_result = await ComposioService().execute_tool(
|
||||
connected_account_id=cca_id,
|
||||
tool_name="GOOGLECALENDAR_PATCH_EVENT",
|
||||
params=composio_params,
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
)
|
||||
if not composio_result.get("success"):
|
||||
raise RuntimeError(
|
||||
composio_result.get(
|
||||
"error", "Unknown Composio Calendar error"
|
||||
)
|
||||
)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
updated = composio_result.get("data", {})
|
||||
if isinstance(updated, dict):
|
||||
updated = updated.get("data", updated)
|
||||
if isinstance(updated, dict):
|
||||
updated = updated.get("response_data", updated)
|
||||
else:
|
||||
service = await asyncio.get_event_loop().run_in_executor(
|
||||
None, lambda: build("calendar", "v3", credentials=creds)
|
||||
)
|
||||
updated = await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
lambda: (
|
||||
service.events()
|
||||
.patch(
|
||||
calendarId="primary",
|
||||
eventId=final_event_id,
|
||||
body=update_body,
|
||||
)
|
||||
.execute()
|
||||
),
|
||||
)
|
||||
except Exception as api_err:
|
||||
from googleapiclient.errors import HttpError
|
||||
|
||||
|
|
|
|||
|
|
@ -179,29 +179,59 @@ def create_create_google_drive_file_tool(
|
|||
f"Creating Google Drive file: name='{final_name}', type='{final_file_type}', connector={actual_connector_id}"
|
||||
)
|
||||
|
||||
pre_built_creds = None
|
||||
if (
|
||||
is_composio_drive = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_drive:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
pre_built_creds = build_composio_credentials(cca_id)
|
||||
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this Drive connector.",
|
||||
}
|
||||
client = GoogleDriveClient(
|
||||
session=db_session,
|
||||
connector_id=actual_connector_id,
|
||||
credentials=pre_built_creds,
|
||||
)
|
||||
try:
|
||||
created = await client.create_file(
|
||||
name=final_name,
|
||||
mime_type=mime_type,
|
||||
parent_folder_id=final_parent_folder_id,
|
||||
content=final_content,
|
||||
)
|
||||
if is_composio_drive:
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
params: dict[str, Any] = {
|
||||
"name": final_name,
|
||||
"mimeType": mime_type,
|
||||
"fields": "id,name,webViewLink,mimeType",
|
||||
}
|
||||
if final_parent_folder_id:
|
||||
params["parents"] = [final_parent_folder_id]
|
||||
if final_content:
|
||||
params["description"] = final_content[:4096]
|
||||
|
||||
result = await ComposioService().execute_tool(
|
||||
connected_account_id=cca_id,
|
||||
tool_name="GOOGLEDRIVE_CREATE_FILE",
|
||||
params=params,
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
)
|
||||
if not result.get("success"):
|
||||
raise RuntimeError(
|
||||
result.get("error", "Unknown Composio Drive error")
|
||||
)
|
||||
created = result.get("data", {})
|
||||
if isinstance(created, dict):
|
||||
created = created.get("data", created)
|
||||
if isinstance(created, dict):
|
||||
created = created.get("response_data", created)
|
||||
if not isinstance(created, dict):
|
||||
created = {}
|
||||
else:
|
||||
created = await client.create_file(
|
||||
name=final_name,
|
||||
mime_type=mime_type,
|
||||
parent_folder_id=final_parent_folder_id,
|
||||
content=final_content,
|
||||
)
|
||||
except HttpError as http_err:
|
||||
if http_err.resp.status == 403:
|
||||
logger.warning(
|
||||
|
|
|
|||
|
|
@ -158,24 +158,38 @@ def create_delete_google_drive_file_tool(
|
|||
f"Deleting Google Drive file: file_id='{final_file_id}', connector={final_connector_id}"
|
||||
)
|
||||
|
||||
pre_built_creds = None
|
||||
if (
|
||||
is_composio_drive = (
|
||||
connector.connector_type
|
||||
== SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR
|
||||
):
|
||||
from app.utils.google_credentials import build_composio_credentials
|
||||
|
||||
)
|
||||
if is_composio_drive:
|
||||
cca_id = connector.config.get("composio_connected_account_id")
|
||||
if cca_id:
|
||||
pre_built_creds = build_composio_credentials(cca_id)
|
||||
if not cca_id:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Composio connected account ID not found for this Drive connector.",
|
||||
}
|
||||
|
||||
client = GoogleDriveClient(
|
||||
session=db_session,
|
||||
connector_id=connector.id,
|
||||
credentials=pre_built_creds,
|
||||
)
|
||||
try:
|
||||
await client.trash_file(file_id=final_file_id)
|
||||
if is_composio_drive:
|
||||
from app.services.composio_service import ComposioService
|
||||
|
||||
result = await ComposioService().execute_tool(
|
||||
connected_account_id=cca_id,
|
||||
tool_name="GOOGLEDRIVE_TRASH_FILE",
|
||||
params={"file_id": final_file_id},
|
||||
entity_id=f"surfsense_{user_id}",
|
||||
)
|
||||
if not result.get("success"):
|
||||
raise RuntimeError(
|
||||
result.get("error", "Unknown Composio Drive error")
|
||||
)
|
||||
else:
|
||||
await client.trash_file(file_id=final_file_id)
|
||||
except HttpError as http_err:
|
||||
if http_err.resp.status == 403:
|
||||
logger.warning(
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ DEFAULT_AUTO_APPROVED_TOOLS: frozenset[str] = frozenset(
|
|||
{
|
||||
"create_gmail_draft",
|
||||
"update_gmail_draft",
|
||||
"create_calendar_event",
|
||||
"create_notion_page",
|
||||
"create_confluence_page",
|
||||
"create_google_drive_file",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue