feat: enhance permission handling and user feedback for Gmail and Google Calendar tools

- Implemented logic to persist authentication expiration status for connectors when insufficient permissions are detected, improving error handling and user experience.
- Updated messages to guide users to re-authenticate in connector settings for Gmail, Google Calendar, and Google Drive tools.
- Added InsufficientPermissionsResult type and corresponding UI components to display permission-related messages consistently across Gmail and Google Calendar tools.
This commit is contained in:
Anish Sarkar 2026-03-20 19:36:00 +05:30
parent f4c0c8c945
commit 283b4194cc
18 changed files with 423 additions and 123 deletions

View file

@ -263,10 +263,29 @@ def create_create_gmail_draft_tool(
logger.warning(
f"Insufficient permissions for connector {actual_connector_id}: {api_err}"
)
try:
from sqlalchemy.orm.attributes import flag_modified
_res = await db_session.execute(
select(SearchSourceConnector).where(
SearchSourceConnector.id == actual_connector_id
)
)
_conn = _res.scalar_one_or_none()
if _conn and not _conn.config.get("auth_expired"):
_conn.config = {**_conn.config, "auth_expired": True}
flag_modified(_conn, "config")
await db_session.commit()
except Exception:
logger.warning(
"Failed to persist auth_expired for connector %s",
actual_connector_id,
exc_info=True,
)
return {
"status": "insufficient_permissions",
"connector_id": actual_connector_id,
"message": "This Gmail account needs additional permissions. Please re-authenticate.",
"message": "This Gmail account needs additional permissions. Please re-authenticate in connector settings.",
}
raise

View file

@ -264,10 +264,29 @@ def create_send_gmail_email_tool(
logger.warning(
f"Insufficient permissions for connector {actual_connector_id}: {api_err}"
)
try:
from sqlalchemy.orm.attributes import flag_modified
_res = await db_session.execute(
select(SearchSourceConnector).where(
SearchSourceConnector.id == actual_connector_id
)
)
_conn = _res.scalar_one_or_none()
if _conn and not _conn.config.get("auth_expired"):
_conn.config = {**_conn.config, "auth_expired": True}
flag_modified(_conn, "config")
await db_session.commit()
except Exception:
logger.warning(
"Failed to persist auth_expired for connector %s",
actual_connector_id,
exc_info=True,
)
return {
"status": "insufficient_permissions",
"connector_id": actual_connector_id,
"message": "This Gmail account needs additional permissions. Please re-authenticate.",
"message": "This Gmail account needs additional permissions. Please re-authenticate in connector settings.",
}
raise

View file

@ -254,10 +254,23 @@ def create_trash_gmail_email_tool(
logger.warning(
f"Insufficient permissions for connector {connector.id}: {api_err}"
)
try:
from sqlalchemy.orm.attributes import flag_modified
if not connector.config.get("auth_expired"):
connector.config = {**connector.config, "auth_expired": True}
flag_modified(connector, "config")
await db_session.commit()
except Exception:
logger.warning(
"Failed to persist auth_expired for connector %s",
connector.id,
exc_info=True,
)
return {
"status": "insufficient_permissions",
"connector_id": connector.id,
"message": "This Gmail account needs additional permissions. Please re-authenticate.",
"message": "This Gmail account needs additional permissions. Please re-authenticate in connector settings.",
}
raise

View file

@ -251,12 +251,45 @@ def create_create_calendar_event_tool(
{"email": e.strip()} for e in final_attendees if e.strip()
]
created = await asyncio.get_event_loop().run_in_executor(
None,
lambda: service.events()
.insert(calendarId="primary", body=event_body)
.execute(),
)
try:
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
if isinstance(api_err, HttpError) and api_err.resp.status == 403:
logger.warning(
f"Insufficient permissions for connector {actual_connector_id}: {api_err}"
)
try:
from sqlalchemy.orm.attributes import flag_modified
_res = await db_session.execute(
select(SearchSourceConnector).where(
SearchSourceConnector.id == actual_connector_id
)
)
_conn = _res.scalar_one_or_none()
if _conn and not _conn.config.get("auth_expired"):
_conn.config = {**_conn.config, "auth_expired": True}
flag_modified(_conn, "config")
await db_session.commit()
except Exception:
logger.warning(
"Failed to persist auth_expired for connector %s",
actual_connector_id,
exc_info=True,
)
return {
"status": "insufficient_permissions",
"connector_id": actual_connector_id,
"message": "This Google Calendar account needs additional permissions. Please re-authenticate in connector settings.",
}
raise
logger.info(
f"Calendar event created: id={created.get('id')}, summary={created.get('summary')}"

View file

@ -230,12 +230,45 @@ def create_delete_calendar_event_tool(
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(),
)
try:
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
if isinstance(api_err, HttpError) and api_err.resp.status == 403:
logger.warning(
f"Insufficient permissions for connector {actual_connector_id}: {api_err}"
)
try:
from sqlalchemy.orm.attributes import flag_modified
_res = await db_session.execute(
select(SearchSourceConnector).where(
SearchSourceConnector.id == actual_connector_id
)
)
_conn = _res.scalar_one_or_none()
if _conn and not _conn.config.get("auth_expired"):
_conn.config = {**_conn.config, "auth_expired": True}
flag_modified(_conn, "config")
await db_session.commit()
except Exception:
logger.warning(
"Failed to persist auth_expired for connector %s",
actual_connector_id,
exc_info=True,
)
return {
"status": "insufficient_permissions",
"connector_id": actual_connector_id,
"message": "This Google Calendar account needs additional permissions. Please re-authenticate in connector settings.",
}
raise
logger.info(
f"Calendar event deleted: event_id={final_event_id}"

View file

@ -271,12 +271,45 @@ def create_update_calendar_event_tool(
"message": "No changes specified. Please provide at least one field to update.",
}
updated = await asyncio.get_event_loop().run_in_executor(
None,
lambda: service.events()
.patch(calendarId="primary", eventId=final_event_id, body=update_body)
.execute(),
)
try:
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
if isinstance(api_err, HttpError) and api_err.resp.status == 403:
logger.warning(
f"Insufficient permissions for connector {actual_connector_id}: {api_err}"
)
try:
from sqlalchemy.orm.attributes import flag_modified
_res = await db_session.execute(
select(SearchSourceConnector).where(
SearchSourceConnector.id == actual_connector_id
)
)
_conn = _res.scalar_one_or_none()
if _conn and not _conn.config.get("auth_expired"):
_conn.config = {**_conn.config, "auth_expired": True}
flag_modified(_conn, "config")
await db_session.commit()
except Exception:
logger.warning(
"Failed to persist auth_expired for connector %s",
actual_connector_id,
exc_info=True,
)
return {
"status": "insufficient_permissions",
"connector_id": actual_connector_id,
"message": "This Google Calendar account needs additional permissions. Please re-authenticate in connector settings.",
}
raise
logger.info(
f"Calendar event updated: event_id={final_event_id}"

View file

@ -232,10 +232,29 @@ def create_create_google_drive_file_tool(
logger.warning(
f"Insufficient permissions for connector {actual_connector_id}: {http_err}"
)
try:
from sqlalchemy.orm.attributes import flag_modified
_res = await db_session.execute(
select(SearchSourceConnector).where(
SearchSourceConnector.id == actual_connector_id
)
)
_conn = _res.scalar_one_or_none()
if _conn and not _conn.config.get("auth_expired"):
_conn.config = {**_conn.config, "auth_expired": True}
flag_modified(_conn, "config")
await db_session.commit()
except Exception:
logger.warning(
"Failed to persist auth_expired for connector %s",
actual_connector_id,
exc_info=True,
)
return {
"status": "insufficient_permissions",
"connector_id": actual_connector_id,
"message": "This Google Drive account needs additional permissions. Please re-authenticate.",
"message": "This Google Drive account needs additional permissions. Please re-authenticate in connector settings.",
}
raise

View file

@ -207,10 +207,23 @@ def create_delete_google_drive_file_tool(
logger.warning(
f"Insufficient permissions for connector {connector.id}: {http_err}"
)
try:
from sqlalchemy.orm.attributes import flag_modified
if not connector.config.get("auth_expired"):
connector.config = {**connector.config, "auth_expired": True}
flag_modified(connector, "config")
await db_session.commit()
except Exception:
logger.warning(
"Failed to persist auth_expired for connector %s",
connector.id,
exc_info=True,
)
return {
"status": "insufficient_permissions",
"connector_id": connector.id,
"message": "This Google Drive account needs additional permissions. Please re-authenticate.",
"message": "This Google Drive account needs additional permissions. Please re-authenticate in connector settings.",
}
raise