From de8841fb86f615b5e4e25d604e2adc3635afe63d Mon Sep 17 00:00:00 2001
From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com>
Date: Sat, 21 Mar 2026 13:20:13 +0530
Subject: [PATCH] chore: ran linting
---
.../app/agents/new_chat/chat_deepagent.py | 6 +-
.../new_chat/tools/confluence/create_page.py | 80 ++++--
.../new_chat/tools/confluence/delete_page.py | 83 ++++--
.../new_chat/tools/confluence/update_page.py | 101 +++++--
.../new_chat/tools/gmail/create_draft.py | 23 +-
.../agents/new_chat/tools/gmail/send_email.py | 19 +-
.../new_chat/tools/gmail/trash_email.py | 24 +-
.../new_chat/tools/gmail/update_draft.py | 27 +-
.../tools/google_calendar/create_event.py | 21 +-
.../tools/google_calendar/delete_event.py | 21 +-
.../tools/google_calendar/update_event.py | 67 +++--
.../tools/google_drive/create_file.py | 5 +-
.../new_chat/tools/google_drive/trash_file.py | 10 +-
.../new_chat/tools/jira/create_issue.py | 73 +++--
.../new_chat/tools/jira/delete_issue.py | 73 +++--
.../new_chat/tools/jira/update_issue.py | 102 +++++--
.../agents/new_chat/tools/knowledge_base.py | 5 +-
.../new_chat/tools/notion/create_page.py | 4 +-
.../new_chat/tools/notion/delete_page.py | 4 +-
.../new_chat/tools/notion/update_page.py | 4 +-
surfsense_backend/app/app.py | 2 +-
.../app/connectors/confluence_history.py | 12 +-
.../connectors/google_calendar_connector.py | 4 +-
.../app/connectors/google_gmail_connector.py | 14 +-
.../app/retriever/chunks_hybrid_search.py | 4 +-
.../app/retriever/documents_hybrid_search.py | 4 +-
.../app/routes/composio_routes.py | 49 +++-
.../google_drive_add_connector_route.py | 16 +-
.../app/routes/linear_add_connector_route.py | 4 +-
.../routes/search_source_connectors_routes.py | 10 +-
.../app/services/composio_service.py | 4 +-
.../services/confluence/kb_sync_service.py | 27 +-
.../confluence/tool_metadata_service.py | 33 ++-
.../services/gmail/tool_metadata_service.py | 38 +--
.../google_calendar/kb_sync_service.py | 49 +++-
.../google_calendar/tool_metadata_service.py | 68 +++--
.../services/google_drive/kb_sync_service.py | 8 +-
.../google_drive/tool_metadata_service.py | 20 +-
.../app/services/jira/kb_sync_service.py | 47 ++-
.../services/jira/tool_metadata_service.py | 23 +-
.../app/services/linear/kb_sync_service.py | 4 +-
.../services/linear/tool_metadata_service.py | 6 +-
.../services/notion/tool_metadata_service.py | 5 +-
.../google_calendar_indexer.py | 21 +-
.../google_drive_indexer.py | 58 ++--
.../google_gmail_indexer.py | 21 +-
.../document_processors/file_processors.py | 19 +-
.../google_unification/conftest.py | 8 +-
.../test_calendar_indexer_credentials.py | 68 +++--
.../test_drive_indexer_credentials.py | 11 +-
.../test_gmail_indexer_credentials.py | 60 +++-
.../test_hybrid_search_type_filtering.py | 4 +-
.../test_connector_credential_acceptance.py | 32 ++-
.../test_schedule_checker_routing.py | 45 ++-
.../app/(home)/login/LocalLoginForm.tsx | 6 +-
.../connectors/callback/route.ts | 5 +-
.../components/DocumentsTableShell.tsx | 20 +-
.../new-chat/[[...chat_id]]/page.tsx | 20 +-
.../[search_space_id]/team/team-content.tsx | 2 +-
surfsense_web/app/global-error.tsx | 2 +-
.../atoms/chat/hitl-edit-panel.atom.ts | 4 +-
.../assistant-ui/connector-popup.tsx | 71 +++--
.../components/elasticsearch-connect-form.tsx | 2 +-
.../components/composio-drive-config.tsx | 84 +++---
.../components/elasticsearch-config.tsx | 2 +-
.../components/google-drive-config.tsx | 35 ++-
.../views/connector-edit-view.tsx | 56 ++--
.../hooks/use-connector-dialog.ts | 227 +++++++--------
.../views/connector-accounts-list-view.tsx | 7 +-
.../assistant-ui/thinking-steps.tsx | 6 +-
.../components/assistant-ui/thread.tsx | 269 +++++++++---------
.../connectors/composio-drive-folder-tree.tsx | 37 ++-
.../hitl-edit-panel/hitl-edit-panel.tsx | 29 +-
.../layout/providers/LayoutDataProvider.tsx | 17 +-
.../layout/ui/right-panel/RightPanel.tsx | 24 +-
.../ui/sidebar/AllPrivateChatsSidebar.tsx | 2 +-
.../ui/sidebar/AllSharedChatsSidebar.tsx | 2 +-
.../settings/image-model-manager.tsx | 4 +-
.../settings/model-config-manager.tsx | 2 +-
.../components/settings/team-dialog.tsx | 2 +-
.../settings/user-settings-dialog.tsx | 2 +-
.../confluence/create-confluence-page.tsx | 55 ++--
.../confluence/delete-confluence-page.tsx | 45 ++-
.../confluence/update-confluence-page.tsx | 69 +++--
.../components/tool-ui/gmail/create-draft.tsx | 80 ++++--
.../components/tool-ui/gmail/send-email.tsx | 133 +++++----
.../components/tool-ui/gmail/trash-email.tsx | 25 +-
.../components/tool-ui/gmail/update-draft.tsx | 105 ++-----
.../tool-ui/google-calendar/create-event.tsx | 124 +++++---
.../tool-ui/google-calendar/delete-event.tsx | 25 +-
.../tool-ui/google-calendar/index.ts | 2 +-
.../tool-ui/google-calendar/update-event.tsx | 143 +++++++---
.../tool-ui/google-drive/create-file.tsx | 161 ++++++-----
.../tool-ui/google-drive/trash-file.tsx | 82 +++---
.../tool-ui/jira/create-jira-issue.tsx | 83 +++---
.../tool-ui/jira/delete-jira-issue.tsx | 20 +-
.../tool-ui/jira/update-jira-issue.tsx | 63 ++--
.../tool-ui/linear/create-linear-issue.tsx | 160 ++++++-----
.../tool-ui/linear/delete-linear-issue.tsx | 62 ++--
.../tool-ui/linear/update-linear-issue.tsx | 79 +++--
.../tool-ui/notion/create-notion-page.tsx | 116 ++++----
.../tool-ui/notion/delete-notion-page.tsx | 22 +-
.../tool-ui/notion/update-notion-page.tsx | 42 ++-
surfsense_web/components/ui/button.tsx | 3 +-
surfsense_web/components/ui/hero-carousel.tsx | 4 +-
surfsense_web/components/ui/radio-group.tsx | 68 ++---
surfsense_web/components/ui/toggle-group.tsx | 138 +++++----
surfsense_web/components/ui/toggle.tsx | 77 +++--
surfsense_web/hooks/use-google-picker.ts | 114 ++++----
surfsense_web/next.config.ts | 2 +-
110 files changed, 2673 insertions(+), 1918 deletions(-)
diff --git a/surfsense_backend/app/agents/new_chat/chat_deepagent.py b/surfsense_backend/app/agents/new_chat/chat_deepagent.py
index c1ad36252..a00cb4b7b 100644
--- a/surfsense_backend/app/agents/new_chat/chat_deepagent.py
+++ b/surfsense_backend/app/agents/new_chat/chat_deepagent.py
@@ -298,8 +298,7 @@ async def create_surfsense_deep_agent(
# Disable Google Drive action tools if no Google Drive connector is configured
has_google_drive_connector = (
- available_connectors is not None
- and "GOOGLE_DRIVE_FILE" in available_connectors
+ available_connectors is not None and "GOOGLE_DRIVE_FILE" in available_connectors
)
if not has_google_drive_connector:
google_drive_tools = [
@@ -337,8 +336,7 @@ async def create_surfsense_deep_agent(
# Disable Jira action tools if no Jira connector is configured
has_jira_connector = (
- available_connectors is not None
- and "JIRA_CONNECTOR" in available_connectors
+ available_connectors is not None and "JIRA_CONNECTOR" in available_connectors
)
if not has_jira_connector:
jira_tools = [
diff --git a/surfsense_backend/app/agents/new_chat/tools/confluence/create_page.py b/surfsense_backend/app/agents/new_chat/tools/confluence/create_page.py
index 6e69ca591..b7decbe0c 100644
--- a/surfsense_backend/app/agents/new_chat/tools/confluence/create_page.py
+++ b/surfsense_backend/app/agents/new_chat/tools/confluence/create_page.py
@@ -43,11 +43,16 @@ def create_create_confluence_page_tool(
logger.info(f"create_confluence_page called: title='{title}'")
if db_session is None or search_space_id is None or user_id is None:
- return {"status": "error", "message": "Confluence tool not properly configured."}
+ return {
+ "status": "error",
+ "message": "Confluence tool not properly configured.",
+ }
try:
metadata_service = ConfluenceToolMetadataService(db_session)
- context = await metadata_service.get_creation_context(search_space_id, user_id)
+ context = await metadata_service.get_creation_context(
+ search_space_id, user_id
+ )
if "error" in context:
return {"status": "error", "message": context["error"]}
@@ -60,22 +65,28 @@ def create_create_confluence_page_tool(
"connector_type": "confluence",
}
- approval = interrupt({
- "type": "confluence_page_creation",
- "action": {
- "tool": "create_confluence_page",
- "params": {
- "title": title,
- "content": content,
- "space_id": space_id,
- "connector_id": connector_id,
+ approval = interrupt(
+ {
+ "type": "confluence_page_creation",
+ "action": {
+ "tool": "create_confluence_page",
+ "params": {
+ "title": title,
+ "content": content,
+ "space_id": space_id,
+ "connector_id": connector_id,
+ },
},
- },
- "context": context,
- })
+ "context": context,
+ }
+ )
- decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else []
- decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ decisions_raw = (
+ approval.get("decisions", []) if isinstance(approval, dict) else []
+ )
+ decisions = (
+ decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ )
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
@@ -84,7 +95,10 @@ def create_create_confluence_page_tool(
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
- return {"status": "rejected", "message": "User declined. The page was not created."}
+ return {
+ "status": "rejected",
+ "message": "User declined. The page was not created.",
+ }
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
@@ -114,12 +128,16 @@ def create_create_confluence_page_tool(
select(SearchSourceConnector).filter(
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type == SearchSourceConnectorType.CONFLUENCE_CONNECTOR,
+ SearchSourceConnector.connector_type
+ == SearchSourceConnectorType.CONFLUENCE_CONNECTOR,
)
)
connector = result.scalars().first()
if not connector:
- return {"status": "error", "message": "No Confluence connector found."}
+ return {
+ "status": "error",
+ "message": "No Confluence connector found.",
+ }
actual_connector_id = connector.id
else:
result = await db_session.execute(
@@ -127,15 +145,21 @@ def create_create_confluence_page_tool(
SearchSourceConnector.id == actual_connector_id,
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type == SearchSourceConnectorType.CONFLUENCE_CONNECTOR,
+ SearchSourceConnector.connector_type
+ == SearchSourceConnectorType.CONFLUENCE_CONNECTOR,
)
)
connector = result.scalars().first()
if not connector:
- return {"status": "error", "message": "Selected Confluence connector is invalid."}
+ return {
+ "status": "error",
+ "message": "Selected Confluence connector is invalid.",
+ }
try:
- client = ConfluenceHistoryConnector(session=db_session, connector_id=actual_connector_id)
+ client = ConfluenceHistoryConnector(
+ session=db_session, connector_id=actual_connector_id
+ )
api_result = await client.create_page(
space_id=final_space_id,
title=final_title,
@@ -143,7 +167,10 @@ def create_create_confluence_page_tool(
)
await client.close()
except Exception as api_err:
- if "http 403" in str(api_err).lower() or "status code 403" in str(api_err).lower():
+ if (
+ "http 403" in str(api_err).lower()
+ or "status code 403" in str(api_err).lower()
+ ):
try:
_conn = connector
_conn.config = {**_conn.config, "auth_expired": True}
@@ -163,6 +190,7 @@ def create_create_confluence_page_tool(
kb_message_suffix = ""
try:
from app.services.confluence import ConfluenceKBSyncService
+
kb_service = ConfluenceKBSyncService(db_session)
kb_result = await kb_service.sync_after_create(
page_id=page_id,
@@ -189,9 +217,13 @@ def create_create_confluence_page_tool(
except Exception as e:
from langgraph.errors import GraphInterrupt
+
if isinstance(e, GraphInterrupt):
raise
logger.error(f"Error creating Confluence page: {e}", exc_info=True)
- return {"status": "error", "message": "Something went wrong while creating the page."}
+ return {
+ "status": "error",
+ "message": "Something went wrong while creating the page.",
+ }
return create_confluence_page
diff --git a/surfsense_backend/app/agents/new_chat/tools/confluence/delete_page.py b/surfsense_backend/app/agents/new_chat/tools/confluence/delete_page.py
index e398aed32..285b1058d 100644
--- a/surfsense_backend/app/agents/new_chat/tools/confluence/delete_page.py
+++ b/surfsense_backend/app/agents/new_chat/tools/confluence/delete_page.py
@@ -39,14 +39,21 @@ def create_delete_confluence_page_tool(
- If status is "not_found", relay the message to the user.
- If status is "insufficient_permissions", inform user to re-authenticate.
"""
- logger.info(f"delete_confluence_page called: page_title_or_id='{page_title_or_id}'")
+ logger.info(
+ f"delete_confluence_page called: page_title_or_id='{page_title_or_id}'"
+ )
if db_session is None or search_space_id is None or user_id is None:
- return {"status": "error", "message": "Confluence tool not properly configured."}
+ return {
+ "status": "error",
+ "message": "Confluence tool not properly configured.",
+ }
try:
metadata_service = ConfluenceToolMetadataService(db_session)
- context = await metadata_service.get_deletion_context(search_space_id, user_id, page_title_or_id)
+ context = await metadata_service.get_deletion_context(
+ search_space_id, user_id, page_title_or_id
+ )
if "error" in context:
error_msg = context["error"]
@@ -67,21 +74,27 @@ def create_delete_confluence_page_tool(
document_id = page_data["document_id"]
connector_id_from_context = context.get("account", {}).get("id")
- approval = interrupt({
- "type": "confluence_page_deletion",
- "action": {
- "tool": "delete_confluence_page",
- "params": {
- "page_id": page_id,
- "connector_id": connector_id_from_context,
- "delete_from_kb": delete_from_kb,
+ approval = interrupt(
+ {
+ "type": "confluence_page_deletion",
+ "action": {
+ "tool": "delete_confluence_page",
+ "params": {
+ "page_id": page_id,
+ "connector_id": connector_id_from_context,
+ "delete_from_kb": delete_from_kb,
+ },
},
- },
- "context": context,
- })
+ "context": context,
+ }
+ )
- decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else []
- decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ decisions_raw = (
+ approval.get("decisions", []) if isinstance(approval, dict) else []
+ )
+ decisions = (
+ decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ )
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
@@ -90,7 +103,10 @@ def create_delete_confluence_page_tool(
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
- return {"status": "rejected", "message": "User declined. The page was not deleted."}
+ return {
+ "status": "rejected",
+ "message": "User declined. The page was not deleted.",
+ }
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
@@ -102,33 +118,47 @@ def create_delete_confluence_page_tool(
final_params = decision["args"]
final_page_id = final_params.get("page_id", page_id)
- final_connector_id = final_params.get("connector_id", connector_id_from_context)
+ final_connector_id = final_params.get(
+ "connector_id", connector_id_from_context
+ )
final_delete_from_kb = final_params.get("delete_from_kb", delete_from_kb)
from sqlalchemy.future import select
from app.db import SearchSourceConnector, SearchSourceConnectorType
if not final_connector_id:
- return {"status": "error", "message": "No connector found for this page."}
+ return {
+ "status": "error",
+ "message": "No connector found for this page.",
+ }
result = await db_session.execute(
select(SearchSourceConnector).filter(
SearchSourceConnector.id == final_connector_id,
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type == SearchSourceConnectorType.CONFLUENCE_CONNECTOR,
+ SearchSourceConnector.connector_type
+ == SearchSourceConnectorType.CONFLUENCE_CONNECTOR,
)
)
connector = result.scalars().first()
if not connector:
- return {"status": "error", "message": "Selected Confluence connector is invalid."}
+ return {
+ "status": "error",
+ "message": "Selected Confluence connector is invalid.",
+ }
try:
- client = ConfluenceHistoryConnector(session=db_session, connector_id=final_connector_id)
+ client = ConfluenceHistoryConnector(
+ session=db_session, connector_id=final_connector_id
+ )
await client.delete_page(final_page_id)
await client.close()
except Exception as api_err:
- if "http 403" in str(api_err).lower() or "status code 403" in str(api_err).lower():
+ if (
+ "http 403" in str(api_err).lower()
+ or "status code 403" in str(api_err).lower()
+ ):
try:
connector.config = {**connector.config, "auth_expired": True}
flag_modified(connector, "config")
@@ -146,6 +176,7 @@ def create_delete_confluence_page_tool(
if final_delete_from_kb and document_id:
try:
from app.db import Document
+
doc_result = await db_session.execute(
select(Document).filter(Document.id == document_id)
)
@@ -171,9 +202,13 @@ def create_delete_confluence_page_tool(
except Exception as e:
from langgraph.errors import GraphInterrupt
+
if isinstance(e, GraphInterrupt):
raise
logger.error(f"Error deleting Confluence page: {e}", exc_info=True)
- return {"status": "error", "message": "Something went wrong while deleting the page."}
+ return {
+ "status": "error",
+ "message": "Something went wrong while deleting the page.",
+ }
return delete_confluence_page
diff --git a/surfsense_backend/app/agents/new_chat/tools/confluence/update_page.py b/surfsense_backend/app/agents/new_chat/tools/confluence/update_page.py
index 7c7be1c66..e43aef59d 100644
--- a/surfsense_backend/app/agents/new_chat/tools/confluence/update_page.py
+++ b/surfsense_backend/app/agents/new_chat/tools/confluence/update_page.py
@@ -41,14 +41,21 @@ def create_update_confluence_page_tool(
- If status is "not_found", relay the message to the user.
- If status is "insufficient_permissions", inform user to re-authenticate.
"""
- logger.info(f"update_confluence_page called: page_title_or_id='{page_title_or_id}'")
+ logger.info(
+ f"update_confluence_page called: page_title_or_id='{page_title_or_id}'"
+ )
if db_session is None or search_space_id is None or user_id is None:
- return {"status": "error", "message": "Confluence tool not properly configured."}
+ return {
+ "status": "error",
+ "message": "Confluence tool not properly configured.",
+ }
try:
metadata_service = ConfluenceToolMetadataService(db_session)
- context = await metadata_service.get_update_context(search_space_id, user_id, page_title_or_id)
+ context = await metadata_service.get_update_context(
+ search_space_id, user_id, page_title_or_id
+ )
if "error" in context:
error_msg = context["error"]
@@ -71,24 +78,30 @@ def create_update_confluence_page_tool(
document_id = page_data.get("document_id")
connector_id_from_context = context.get("account", {}).get("id")
- approval = interrupt({
- "type": "confluence_page_update",
- "action": {
- "tool": "update_confluence_page",
- "params": {
- "page_id": page_id,
- "document_id": document_id,
- "new_title": new_title,
- "new_content": new_content,
- "version": current_version,
- "connector_id": connector_id_from_context,
+ approval = interrupt(
+ {
+ "type": "confluence_page_update",
+ "action": {
+ "tool": "update_confluence_page",
+ "params": {
+ "page_id": page_id,
+ "document_id": document_id,
+ "new_title": new_title,
+ "new_content": new_content,
+ "version": current_version,
+ "connector_id": connector_id_from_context,
+ },
},
- },
- "context": context,
- })
+ "context": context,
+ }
+ )
- decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else []
- decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ decisions_raw = (
+ approval.get("decisions", []) if isinstance(approval, dict) else []
+ )
+ decisions = (
+ decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ )
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
@@ -97,7 +110,10 @@ def create_update_confluence_page_tool(
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
- return {"status": "rejected", "message": "User declined. The page was not updated."}
+ return {
+ "status": "rejected",
+ "message": "User declined. The page was not updated.",
+ }
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
@@ -114,29 +130,40 @@ def create_update_confluence_page_tool(
if final_content is None:
final_content = current_body
final_version = final_params.get("version", current_version)
- final_connector_id = final_params.get("connector_id", connector_id_from_context)
+ final_connector_id = final_params.get(
+ "connector_id", connector_id_from_context
+ )
final_document_id = final_params.get("document_id", document_id)
from sqlalchemy.future import select
from app.db import SearchSourceConnector, SearchSourceConnectorType
if not final_connector_id:
- return {"status": "error", "message": "No connector found for this page."}
+ return {
+ "status": "error",
+ "message": "No connector found for this page.",
+ }
result = await db_session.execute(
select(SearchSourceConnector).filter(
SearchSourceConnector.id == final_connector_id,
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type == SearchSourceConnectorType.CONFLUENCE_CONNECTOR,
+ SearchSourceConnector.connector_type
+ == SearchSourceConnectorType.CONFLUENCE_CONNECTOR,
)
)
connector = result.scalars().first()
if not connector:
- return {"status": "error", "message": "Selected Confluence connector is invalid."}
+ return {
+ "status": "error",
+ "message": "Selected Confluence connector is invalid.",
+ }
try:
- client = ConfluenceHistoryConnector(session=db_session, connector_id=final_connector_id)
+ client = ConfluenceHistoryConnector(
+ session=db_session, connector_id=final_connector_id
+ )
await client.update_page(
page_id=final_page_id,
title=final_title,
@@ -145,7 +172,10 @@ def create_update_confluence_page_tool(
)
await client.close()
except Exception as api_err:
- if "http 403" in str(api_err).lower() or "status code 403" in str(api_err).lower():
+ if (
+ "http 403" in str(api_err).lower()
+ or "status code 403" in str(api_err).lower()
+ ):
try:
connector.config = {**connector.config, "auth_expired": True}
flag_modified(connector, "config")
@@ -163,6 +193,7 @@ def create_update_confluence_page_tool(
if final_document_id:
try:
from app.services.confluence import ConfluenceKBSyncService
+
kb_service = ConfluenceKBSyncService(db_session)
kb_result = await kb_service.sync_after_update(
document_id=final_document_id,
@@ -171,12 +202,18 @@ def create_update_confluence_page_tool(
search_space_id=search_space_id,
)
if kb_result["status"] == "success":
- kb_message_suffix = " Your knowledge base has also been updated."
+ kb_message_suffix = (
+ " Your knowledge base has also been updated."
+ )
else:
- kb_message_suffix = " The knowledge base will be updated in the next sync."
+ kb_message_suffix = (
+ " The knowledge base will be updated in the next sync."
+ )
except Exception as kb_err:
logger.warning(f"KB sync after update failed: {kb_err}")
- kb_message_suffix = " The knowledge base will be updated in the next sync."
+ kb_message_suffix = (
+ " The knowledge base will be updated in the next sync."
+ )
return {
"status": "success",
@@ -186,9 +223,13 @@ def create_update_confluence_page_tool(
except Exception as e:
from langgraph.errors import GraphInterrupt
+
if isinstance(e, GraphInterrupt):
raise
logger.error(f"Error updating Confluence page: {e}", exc_info=True)
- return {"status": "error", "message": "Something went wrong while updating the page."}
+ return {
+ "status": "error",
+ "message": "Something went wrong while updating the page.",
+ }
return update_confluence_page
diff --git a/surfsense_backend/app/agents/new_chat/tools/gmail/create_draft.py b/surfsense_backend/app/agents/new_chat/tools/gmail/create_draft.py
index cd8f5eb37..26de5d394 100644
--- a/surfsense_backend/app/agents/new_chat/tools/gmail/create_draft.py
+++ b/surfsense_backend/app/agents/new_chat/tools/gmail/create_draft.py
@@ -55,9 +55,7 @@ def create_create_gmail_draft_tool(
- "Draft an email to alice@example.com about the meeting"
- "Compose a reply to Bob about the project update"
"""
- logger.info(
- f"create_gmail_draft called: to='{to}', subject='{subject}'"
- )
+ logger.info(f"create_gmail_draft called: to='{to}', subject='{subject}'")
if db_session is None or search_space_id is None or user_id is None:
return {
@@ -187,7 +185,10 @@ def create_create_gmail_draft_tool(
f"Creating Gmail draft: to='{final_to}', subject='{final_subject}', connector={actual_connector_id}"
)
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -251,10 +252,12 @@ def create_create_gmail_draft_tool(
try:
created = await asyncio.get_event_loop().run_in_executor(
None,
- lambda: gmail_service.users()
- .drafts()
- .create(userId="me", body={"message": {"raw": raw}})
- .execute(),
+ lambda: (
+ gmail_service.users()
+ .drafts()
+ .create(userId="me", body={"message": {"raw": raw}})
+ .execute()
+ ),
)
except Exception as api_err:
from googleapiclient.errors import HttpError
@@ -289,9 +292,7 @@ def create_create_gmail_draft_tool(
}
raise
- logger.info(
- f"Gmail draft created: id={created.get('id')}"
- )
+ logger.info(f"Gmail draft created: id={created.get('id')}")
kb_message_suffix = ""
try:
diff --git a/surfsense_backend/app/agents/new_chat/tools/gmail/send_email.py b/surfsense_backend/app/agents/new_chat/tools/gmail/send_email.py
index dd4d66a57..1b46d405a 100644
--- a/surfsense_backend/app/agents/new_chat/tools/gmail/send_email.py
+++ b/surfsense_backend/app/agents/new_chat/tools/gmail/send_email.py
@@ -56,9 +56,7 @@ def create_send_gmail_email_tool(
- "Send an email to alice@example.com about the meeting"
- "Email Bob the project update"
"""
- logger.info(
- f"send_gmail_email called: to='{to}', subject='{subject}'"
- )
+ logger.info(f"send_gmail_email called: to='{to}', subject='{subject}'")
if db_session is None or search_space_id is None or user_id is None:
return {
@@ -188,7 +186,10 @@ def create_send_gmail_email_tool(
f"Sending Gmail email: to='{final_to}', subject='{final_subject}', connector={actual_connector_id}"
)
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -252,10 +253,12 @@ def create_send_gmail_email_tool(
try:
sent = await asyncio.get_event_loop().run_in_executor(
None,
- lambda: gmail_service.users()
- .messages()
- .send(userId="me", body={"raw": raw})
- .execute(),
+ lambda: (
+ gmail_service.users()
+ .messages()
+ .send(userId="me", body={"raw": raw})
+ .execute()
+ ),
)
except Exception as api_err:
from googleapiclient.errors import HttpError
diff --git a/surfsense_backend/app/agents/new_chat/tools/gmail/trash_email.py b/surfsense_backend/app/agents/new_chat/tools/gmail/trash_email.py
index b129888f9..25b669c7d 100644
--- a/surfsense_backend/app/agents/new_chat/tools/gmail/trash_email.py
+++ b/surfsense_backend/app/agents/new_chat/tools/gmail/trash_email.py
@@ -186,7 +186,10 @@ def create_trash_gmail_email_tool(
f"Trashing Gmail email: message_id='{final_message_id}', connector={final_connector_id}"
)
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -241,10 +244,12 @@ def create_trash_gmail_email_tool(
try:
await asyncio.get_event_loop().run_in_executor(
None,
- lambda: gmail_service.users()
- .messages()
- .trash(userId="me", id=final_message_id)
- .execute(),
+ lambda: (
+ gmail_service.users()
+ .messages()
+ .trash(userId="me", id=final_message_id)
+ .execute()
+ ),
)
except Exception as api_err:
from googleapiclient.errors import HttpError
@@ -257,7 +262,10 @@ def create_trash_gmail_email_tool(
from sqlalchemy.orm.attributes import flag_modified
if not connector.config.get("auth_expired"):
- connector.config = {**connector.config, "auth_expired": True}
+ connector.config = {
+ **connector.config,
+ "auth_expired": True,
+ }
flag_modified(connector, "config")
await db_session.commit()
except Exception:
@@ -273,9 +281,7 @@ def create_trash_gmail_email_tool(
}
raise
- logger.info(
- f"Gmail email trashed: message_id={final_message_id}"
- )
+ logger.info(f"Gmail email trashed: message_id={final_message_id}")
trash_result: dict[str, Any] = {
"status": "success",
diff --git a/surfsense_backend/app/agents/new_chat/tools/gmail/update_draft.py b/surfsense_backend/app/agents/new_chat/tools/gmail/update_draft.py
index f5864bd58..bcc884e8d 100644
--- a/surfsense_backend/app/agents/new_chat/tools/gmail/update_draft.py
+++ b/surfsense_backend/app/agents/new_chat/tools/gmail/update_draft.py
@@ -216,7 +216,10 @@ def create_update_gmail_draft_tool(
f"Updating Gmail draft: subject='{final_subject}', connector={final_connector_id}"
)
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -299,14 +302,16 @@ def create_update_gmail_draft_tool(
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(),
+ 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
@@ -369,7 +374,9 @@ def create_update_gmail_draft_tool(
document.document_metadata = meta
flag_modified(document, "document_metadata")
await db_session.commit()
- kb_message_suffix = " Your knowledge base has also been updated."
+ kb_message_suffix = (
+ " Your knowledge base has also been updated."
+ )
logger.info(
f"KB document {document_id} updated for draft {final_draft_id}"
)
diff --git a/surfsense_backend/app/agents/new_chat/tools/google_calendar/create_event.py b/surfsense_backend/app/agents/new_chat/tools/google_calendar/create_event.py
index 14a43975c..1ac51df0e 100644
--- a/surfsense_backend/app/agents/new_chat/tools/google_calendar/create_event.py
+++ b/surfsense_backend/app/agents/new_chat/tools/google_calendar/create_event.py
@@ -78,7 +78,9 @@ def create_create_calendar_event_tool(
accounts = context.get("accounts", [])
if accounts and all(a.get("auth_expired") for a in accounts):
- logger.warning("All Google Calendar accounts have expired authentication")
+ logger.warning(
+ "All Google Calendar accounts have expired authentication"
+ )
return {
"status": "auth_error",
"message": "All connected Google Calendar accounts need re-authentication. Please re-authenticate in your connector settings.",
@@ -194,7 +196,10 @@ def create_create_calendar_event_tool(
f"Creating calendar event: summary='{final_summary}', connector={actual_connector_id}"
)
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -216,7 +221,9 @@ def create_create_calendar_event_tool(
token_encryption = TokenEncryption(app_config.SECRET_KEY)
for key in ("token", "refresh_token", "client_secret"):
if config_data.get(key):
- config_data[key] = token_encryption.decrypt_token(config_data[key])
+ config_data[key] = token_encryption.decrypt_token(
+ config_data[key]
+ )
exp = config_data.get("expiry", "")
if exp:
@@ -254,9 +261,11 @@ 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(),
+ lambda: (
+ service.events()
+ .insert(calendarId="primary", body=event_body)
+ .execute()
+ ),
)
except Exception as api_err:
from googleapiclient.errors import HttpError
diff --git a/surfsense_backend/app/agents/new_chat/tools/google_calendar/delete_event.py b/surfsense_backend/app/agents/new_chat/tools/google_calendar/delete_event.py
index 8baf866b5..7dcfec213 100644
--- a/surfsense_backend/app/agents/new_chat/tools/google_calendar/delete_event.py
+++ b/surfsense_backend/app/agents/new_chat/tools/google_calendar/delete_event.py
@@ -187,7 +187,10 @@ def create_delete_calendar_event_tool(
f"Deleting calendar event: event_id='{final_event_id}', connector={actual_connector_id}"
)
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -209,7 +212,9 @@ def create_delete_calendar_event_tool(
token_encryption = TokenEncryption(app_config.SECRET_KEY)
for key in ("token", "refresh_token", "client_secret"):
if config_data.get(key):
- config_data[key] = token_encryption.decrypt_token(config_data[key])
+ config_data[key] = token_encryption.decrypt_token(
+ config_data[key]
+ )
exp = config_data.get("expiry", "")
if exp:
@@ -232,9 +237,11 @@ def create_delete_calendar_event_tool(
try:
await asyncio.get_event_loop().run_in_executor(
None,
- lambda: service.events()
- .delete(calendarId="primary", eventId=final_event_id)
- .execute(),
+ lambda: (
+ service.events()
+ .delete(calendarId="primary", eventId=final_event_id)
+ .execute()
+ ),
)
except Exception as api_err:
from googleapiclient.errors import HttpError
@@ -269,9 +276,7 @@ def create_delete_calendar_event_tool(
}
raise
- logger.info(
- f"Calendar event deleted: event_id={final_event_id}"
- )
+ logger.info(f"Calendar event deleted: event_id={final_event_id}")
delete_result: dict[str, Any] = {
"status": "success",
diff --git a/surfsense_backend/app/agents/new_chat/tools/google_calendar/update_event.py b/surfsense_backend/app/agents/new_chat/tools/google_calendar/update_event.py
index ba60b6036..bc9707fb5 100644
--- a/surfsense_backend/app/agents/new_chat/tools/google_calendar/update_event.py
+++ b/surfsense_backend/app/agents/new_chat/tools/google_calendar/update_event.py
@@ -58,9 +58,7 @@ def create_update_calendar_event_tool(
- "Reschedule the team standup to 3pm"
- "Change the location of my dentist appointment"
"""
- logger.info(
- f"update_calendar_event called: event_ref='{event_title_or_id}'"
- )
+ logger.info(f"update_calendar_event called: event_ref='{event_title_or_id}'")
if db_session is None or search_space_id is None or user_id is None:
return {
@@ -83,9 +81,7 @@ def create_update_calendar_event_tool(
return {"status": "error", "message": error_msg}
if context.get("auth_expired"):
- logger.warning(
- "Google Calendar account has expired authentication"
- )
+ logger.warning("Google Calendar account has expired authentication")
return {
"status": "auth_error",
"message": "The Google Calendar account for this event needs re-authentication. Please re-authenticate in your connector settings.",
@@ -162,8 +158,12 @@ def create_update_calendar_event_tool(
"connector_id", connector_id_from_context
)
final_new_summary = final_params.get("new_summary", new_summary)
- final_new_start_datetime = final_params.get("new_start_datetime", new_start_datetime)
- final_new_end_datetime = final_params.get("new_end_datetime", new_end_datetime)
+ final_new_start_datetime = final_params.get(
+ "new_start_datetime", new_start_datetime
+ )
+ final_new_end_datetime = final_params.get(
+ "new_end_datetime", new_end_datetime
+ )
final_new_description = final_params.get("new_description", new_description)
final_new_location = final_params.get("new_location", new_location)
final_new_attendees = final_params.get("new_attendees", new_attendees)
@@ -204,7 +204,10 @@ def create_update_calendar_event_tool(
f"Updating calendar event: event_id='{final_event_id}', connector={actual_connector_id}"
)
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -226,7 +229,9 @@ def create_update_calendar_event_tool(
token_encryption = TokenEncryption(app_config.SECRET_KEY)
for key in ("token", "refresh_token", "client_secret"):
if config_data.get(key):
- config_data[key] = token_encryption.decrypt_token(config_data[key])
+ config_data[key] = token_encryption.decrypt_token(
+ config_data[key]
+ )
exp = config_data.get("expiry", "")
if exp:
@@ -250,11 +255,25 @@ def create_update_calendar_event_tool(
if final_new_summary is not None:
update_body["summary"] = final_new_summary
if final_new_start_datetime is not None:
- tz = context.get("timezone", "UTC") if isinstance(context, dict) else "UTC"
- update_body["start"] = {"dateTime": final_new_start_datetime, "timeZone": tz}
+ tz = (
+ context.get("timezone", "UTC")
+ if isinstance(context, dict)
+ else "UTC"
+ )
+ update_body["start"] = {
+ "dateTime": final_new_start_datetime,
+ "timeZone": tz,
+ }
if final_new_end_datetime is not None:
- tz = context.get("timezone", "UTC") if isinstance(context, dict) else "UTC"
- update_body["end"] = {"dateTime": final_new_end_datetime, "timeZone": tz}
+ tz = (
+ context.get("timezone", "UTC")
+ if isinstance(context, dict)
+ else "UTC"
+ )
+ update_body["end"] = {
+ "dateTime": final_new_end_datetime,
+ "timeZone": tz,
+ }
if final_new_description is not None:
update_body["description"] = final_new_description
if final_new_location is not None:
@@ -273,9 +292,15 @@ 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)
- .execute(),
+ lambda: (
+ service.events()
+ .patch(
+ calendarId="primary",
+ eventId=final_event_id,
+ body=update_body,
+ )
+ .execute()
+ ),
)
except Exception as api_err:
from googleapiclient.errors import HttpError
@@ -310,9 +335,7 @@ def create_update_calendar_event_tool(
}
raise
- logger.info(
- f"Calendar event updated: event_id={final_event_id}"
- )
+ logger.info(f"Calendar event updated: event_id={final_event_id}")
kb_message_suffix = ""
if document_id is not None:
@@ -328,7 +351,9 @@ def create_update_calendar_event_tool(
user_id=user_id,
)
if kb_result["status"] == "success":
- kb_message_suffix = " Your knowledge base has also been updated."
+ kb_message_suffix = (
+ " Your knowledge base has also been updated."
+ )
else:
kb_message_suffix = " The knowledge base will be updated in the next scheduled sync."
except Exception as kb_err:
diff --git a/surfsense_backend/app/agents/new_chat/tools/google_drive/create_file.py b/surfsense_backend/app/agents/new_chat/tools/google_drive/create_file.py
index d39e9c640..20c34aafd 100644
--- a/surfsense_backend/app/agents/new_chat/tools/google_drive/create_file.py
+++ b/surfsense_backend/app/agents/new_chat/tools/google_drive/create_file.py
@@ -208,7 +208,10 @@ def create_create_google_drive_file_tool(
)
pre_built_creds = None
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
diff --git a/surfsense_backend/app/agents/new_chat/tools/google_drive/trash_file.py b/surfsense_backend/app/agents/new_chat/tools/google_drive/trash_file.py
index 742e84ad0..206f2e2fd 100644
--- a/surfsense_backend/app/agents/new_chat/tools/google_drive/trash_file.py
+++ b/surfsense_backend/app/agents/new_chat/tools/google_drive/trash_file.py
@@ -187,7 +187,10 @@ def create_delete_google_drive_file_tool(
)
pre_built_creds = None
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR
+ ):
from app.utils.google_credentials import build_composio_credentials
cca_id = connector.config.get("composio_connected_account_id")
@@ -210,7 +213,10 @@ def create_delete_google_drive_file_tool(
from sqlalchemy.orm.attributes import flag_modified
if not connector.config.get("auth_expired"):
- connector.config = {**connector.config, "auth_expired": True}
+ connector.config = {
+ **connector.config,
+ "auth_expired": True,
+ }
flag_modified(connector, "config")
await db_session.commit()
except Exception:
diff --git a/surfsense_backend/app/agents/new_chat/tools/jira/create_issue.py b/surfsense_backend/app/agents/new_chat/tools/jira/create_issue.py
index d072254d4..273cb163b 100644
--- a/surfsense_backend/app/agents/new_chat/tools/jira/create_issue.py
+++ b/surfsense_backend/app/agents/new_chat/tools/jira/create_issue.py
@@ -45,14 +45,18 @@ def create_create_jira_issue_tool(
- If status is "rejected", the user declined. Do NOT retry.
- If status is "insufficient_permissions", inform user to re-authenticate.
"""
- logger.info(f"create_jira_issue called: project_key='{project_key}', summary='{summary}'")
+ logger.info(
+ f"create_jira_issue called: project_key='{project_key}', summary='{summary}'"
+ )
if db_session is None or search_space_id is None or user_id is None:
return {"status": "error", "message": "Jira tool not properly configured."}
try:
metadata_service = JiraToolMetadataService(db_session)
- context = await metadata_service.get_creation_context(search_space_id, user_id)
+ context = await metadata_service.get_creation_context(
+ search_space_id, user_id
+ )
if "error" in context:
return {"status": "error", "message": context["error"]}
@@ -65,24 +69,30 @@ def create_create_jira_issue_tool(
"connector_type": "jira",
}
- approval = interrupt({
- "type": "jira_issue_creation",
- "action": {
- "tool": "create_jira_issue",
- "params": {
- "project_key": project_key,
- "summary": summary,
- "issue_type": issue_type,
- "description": description,
- "priority": priority,
- "connector_id": connector_id,
+ approval = interrupt(
+ {
+ "type": "jira_issue_creation",
+ "action": {
+ "tool": "create_jira_issue",
+ "params": {
+ "project_key": project_key,
+ "summary": summary,
+ "issue_type": issue_type,
+ "description": description,
+ "priority": priority,
+ "connector_id": connector_id,
+ },
},
- },
- "context": context,
- })
+ "context": context,
+ }
+ )
- decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else []
- decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ decisions_raw = (
+ approval.get("decisions", []) if isinstance(approval, dict) else []
+ )
+ decisions = (
+ decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ )
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
@@ -91,7 +101,10 @@ def create_create_jira_issue_tool(
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
- return {"status": "rejected", "message": "User declined. The issue was not created."}
+ return {
+ "status": "rejected",
+ "message": "User declined. The issue was not created.",
+ }
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
@@ -123,7 +136,8 @@ def create_create_jira_issue_tool(
select(SearchSourceConnector).filter(
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type == SearchSourceConnectorType.JIRA_CONNECTOR,
+ SearchSourceConnector.connector_type
+ == SearchSourceConnectorType.JIRA_CONNECTOR,
)
)
connector = result.scalars().first()
@@ -136,15 +150,21 @@ def create_create_jira_issue_tool(
SearchSourceConnector.id == actual_connector_id,
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type == SearchSourceConnectorType.JIRA_CONNECTOR,
+ SearchSourceConnector.connector_type
+ == SearchSourceConnectorType.JIRA_CONNECTOR,
)
)
connector = result.scalars().first()
if not connector:
- return {"status": "error", "message": "Selected Jira connector is invalid."}
+ return {
+ "status": "error",
+ "message": "Selected Jira connector is invalid.",
+ }
try:
- jira_history = JiraHistoryConnector(session=db_session, connector_id=actual_connector_id)
+ jira_history = JiraHistoryConnector(
+ session=db_session, connector_id=actual_connector_id
+ )
jira_client = await jira_history._get_jira_client()
api_result = await asyncio.to_thread(
jira_client.create_issue,
@@ -175,6 +195,7 @@ def create_create_jira_issue_tool(
kb_message_suffix = ""
try:
from app.services.jira import JiraKBSyncService
+
kb_service = JiraKBSyncService(db_session)
kb_result = await kb_service.sync_after_create(
issue_id=issue_key,
@@ -202,9 +223,13 @@ def create_create_jira_issue_tool(
except Exception as e:
from langgraph.errors import GraphInterrupt
+
if isinstance(e, GraphInterrupt):
raise
logger.error(f"Error creating Jira issue: {e}", exc_info=True)
- return {"status": "error", "message": "Something went wrong while creating the issue."}
+ return {
+ "status": "error",
+ "message": "Something went wrong while creating the issue.",
+ }
return create_jira_issue
diff --git a/surfsense_backend/app/agents/new_chat/tools/jira/delete_issue.py b/surfsense_backend/app/agents/new_chat/tools/jira/delete_issue.py
index 46e97d3d5..0f656fbb0 100644
--- a/surfsense_backend/app/agents/new_chat/tools/jira/delete_issue.py
+++ b/surfsense_backend/app/agents/new_chat/tools/jira/delete_issue.py
@@ -40,14 +40,18 @@ def create_delete_jira_issue_tool(
- If status is "not_found", relay the message to the user.
- If status is "insufficient_permissions", inform user to re-authenticate.
"""
- logger.info(f"delete_jira_issue called: issue_title_or_key='{issue_title_or_key}'")
+ logger.info(
+ f"delete_jira_issue called: issue_title_or_key='{issue_title_or_key}'"
+ )
if db_session is None or search_space_id is None or user_id is None:
return {"status": "error", "message": "Jira tool not properly configured."}
try:
metadata_service = JiraToolMetadataService(db_session)
- context = await metadata_service.get_deletion_context(search_space_id, user_id, issue_title_or_key)
+ context = await metadata_service.get_deletion_context(
+ search_space_id, user_id, issue_title_or_key
+ )
if "error" in context:
error_msg = context["error"]
@@ -67,21 +71,27 @@ def create_delete_jira_issue_tool(
document_id = issue_data["document_id"]
connector_id_from_context = context.get("account", {}).get("id")
- approval = interrupt({
- "type": "jira_issue_deletion",
- "action": {
- "tool": "delete_jira_issue",
- "params": {
- "issue_key": issue_key,
- "connector_id": connector_id_from_context,
- "delete_from_kb": delete_from_kb,
+ approval = interrupt(
+ {
+ "type": "jira_issue_deletion",
+ "action": {
+ "tool": "delete_jira_issue",
+ "params": {
+ "issue_key": issue_key,
+ "connector_id": connector_id_from_context,
+ "delete_from_kb": delete_from_kb,
+ },
},
- },
- "context": context,
- })
+ "context": context,
+ }
+ )
- decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else []
- decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ decisions_raw = (
+ approval.get("decisions", []) if isinstance(approval, dict) else []
+ )
+ decisions = (
+ decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ )
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
@@ -90,7 +100,10 @@ def create_delete_jira_issue_tool(
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
- return {"status": "rejected", "message": "User declined. The issue was not deleted."}
+ return {
+ "status": "rejected",
+ "message": "User declined. The issue was not deleted.",
+ }
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
@@ -102,29 +115,40 @@ def create_delete_jira_issue_tool(
final_params = decision["args"]
final_issue_key = final_params.get("issue_key", issue_key)
- final_connector_id = final_params.get("connector_id", connector_id_from_context)
+ final_connector_id = final_params.get(
+ "connector_id", connector_id_from_context
+ )
final_delete_from_kb = final_params.get("delete_from_kb", delete_from_kb)
from sqlalchemy.future import select
from app.db import SearchSourceConnector, SearchSourceConnectorType
if not final_connector_id:
- return {"status": "error", "message": "No connector found for this issue."}
+ return {
+ "status": "error",
+ "message": "No connector found for this issue.",
+ }
result = await db_session.execute(
select(SearchSourceConnector).filter(
SearchSourceConnector.id == final_connector_id,
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type == SearchSourceConnectorType.JIRA_CONNECTOR,
+ SearchSourceConnector.connector_type
+ == SearchSourceConnectorType.JIRA_CONNECTOR,
)
)
connector = result.scalars().first()
if not connector:
- return {"status": "error", "message": "Selected Jira connector is invalid."}
+ return {
+ "status": "error",
+ "message": "Selected Jira connector is invalid.",
+ }
try:
- jira_history = JiraHistoryConnector(session=db_session, connector_id=final_connector_id)
+ jira_history = JiraHistoryConnector(
+ session=db_session, connector_id=final_connector_id
+ )
jira_client = await jira_history._get_jira_client()
await asyncio.to_thread(jira_client.delete_issue, final_issue_key)
except Exception as api_err:
@@ -146,6 +170,7 @@ def create_delete_jira_issue_tool(
if final_delete_from_kb and document_id:
try:
from app.db import Document
+
doc_result = await db_session.execute(
select(Document).filter(Document.id == document_id)
)
@@ -171,9 +196,13 @@ def create_delete_jira_issue_tool(
except Exception as e:
from langgraph.errors import GraphInterrupt
+
if isinstance(e, GraphInterrupt):
raise
logger.error(f"Error deleting Jira issue: {e}", exc_info=True)
- return {"status": "error", "message": "Something went wrong while deleting the issue."}
+ return {
+ "status": "error",
+ "message": "Something went wrong while deleting the issue.",
+ }
return delete_jira_issue
diff --git a/surfsense_backend/app/agents/new_chat/tools/jira/update_issue.py b/surfsense_backend/app/agents/new_chat/tools/jira/update_issue.py
index a3ffa3020..f25eb826f 100644
--- a/surfsense_backend/app/agents/new_chat/tools/jira/update_issue.py
+++ b/surfsense_backend/app/agents/new_chat/tools/jira/update_issue.py
@@ -44,14 +44,18 @@ def create_update_jira_issue_tool(
- If status is "not_found", relay the message and ask user to verify.
- If status is "insufficient_permissions", inform user to re-authenticate.
"""
- logger.info(f"update_jira_issue called: issue_title_or_key='{issue_title_or_key}'")
+ logger.info(
+ f"update_jira_issue called: issue_title_or_key='{issue_title_or_key}'"
+ )
if db_session is None or search_space_id is None or user_id is None:
return {"status": "error", "message": "Jira tool not properly configured."}
try:
metadata_service = JiraToolMetadataService(db_session)
- context = await metadata_service.get_update_context(search_space_id, user_id, issue_title_or_key)
+ context = await metadata_service.get_update_context(
+ search_space_id, user_id, issue_title_or_key
+ )
if "error" in context:
error_msg = context["error"]
@@ -71,24 +75,30 @@ def create_update_jira_issue_tool(
document_id = issue_data.get("document_id")
connector_id_from_context = context.get("account", {}).get("id")
- approval = interrupt({
- "type": "jira_issue_update",
- "action": {
- "tool": "update_jira_issue",
- "params": {
- "issue_key": issue_key,
- "document_id": document_id,
- "new_summary": new_summary,
- "new_description": new_description,
- "new_priority": new_priority,
- "connector_id": connector_id_from_context,
+ approval = interrupt(
+ {
+ "type": "jira_issue_update",
+ "action": {
+ "tool": "update_jira_issue",
+ "params": {
+ "issue_key": issue_key,
+ "document_id": document_id,
+ "new_summary": new_summary,
+ "new_description": new_description,
+ "new_priority": new_priority,
+ "connector_id": connector_id_from_context,
+ },
},
- },
- "context": context,
- })
+ "context": context,
+ }
+ )
- decisions_raw = approval.get("decisions", []) if isinstance(approval, dict) else []
- decisions = decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ decisions_raw = (
+ approval.get("decisions", []) if isinstance(approval, dict) else []
+ )
+ decisions = (
+ decisions_raw if isinstance(decisions_raw, list) else [decisions_raw]
+ )
decisions = [d for d in decisions if isinstance(d, dict)]
if not decisions:
return {"status": "error", "message": "No approval decision received"}
@@ -97,7 +107,10 @@ def create_update_jira_issue_tool(
decision_type = decision.get("type") or decision.get("decision_type")
if decision_type == "reject":
- return {"status": "rejected", "message": "User declined. The issue was not updated."}
+ return {
+ "status": "rejected",
+ "message": "User declined. The issue was not updated.",
+ }
final_params: dict[str, Any] = {}
edited_action = decision.get("edited_action")
@@ -112,26 +125,35 @@ def create_update_jira_issue_tool(
final_summary = final_params.get("new_summary", new_summary)
final_description = final_params.get("new_description", new_description)
final_priority = final_params.get("new_priority", new_priority)
- final_connector_id = final_params.get("connector_id", connector_id_from_context)
+ final_connector_id = final_params.get(
+ "connector_id", connector_id_from_context
+ )
final_document_id = final_params.get("document_id", document_id)
from sqlalchemy.future import select
from app.db import SearchSourceConnector, SearchSourceConnectorType
if not final_connector_id:
- return {"status": "error", "message": "No connector found for this issue."}
+ return {
+ "status": "error",
+ "message": "No connector found for this issue.",
+ }
result = await db_session.execute(
select(SearchSourceConnector).filter(
SearchSourceConnector.id == final_connector_id,
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type == SearchSourceConnectorType.JIRA_CONNECTOR,
+ SearchSourceConnector.connector_type
+ == SearchSourceConnectorType.JIRA_CONNECTOR,
)
)
connector = result.scalars().first()
if not connector:
- return {"status": "error", "message": "Selected Jira connector is invalid."}
+ return {
+ "status": "error",
+ "message": "Selected Jira connector is invalid.",
+ }
fields: dict[str, Any] = {}
if final_summary:
@@ -140,7 +162,12 @@ def create_update_jira_issue_tool(
fields["description"] = {
"type": "doc",
"version": 1,
- "content": [{"type": "paragraph", "content": [{"type": "text", "text": final_description}]}],
+ "content": [
+ {
+ "type": "paragraph",
+ "content": [{"type": "text", "text": final_description}],
+ }
+ ],
}
if final_priority:
fields["priority"] = {"name": final_priority}
@@ -149,9 +176,13 @@ def create_update_jira_issue_tool(
return {"status": "error", "message": "No changes specified."}
try:
- jira_history = JiraHistoryConnector(session=db_session, connector_id=final_connector_id)
+ jira_history = JiraHistoryConnector(
+ session=db_session, connector_id=final_connector_id
+ )
jira_client = await jira_history._get_jira_client()
- await asyncio.to_thread(jira_client.update_issue, final_issue_key, fields)
+ await asyncio.to_thread(
+ jira_client.update_issue, final_issue_key, fields
+ )
except Exception as api_err:
if "status code 403" in str(api_err).lower():
try:
@@ -171,6 +202,7 @@ def create_update_jira_issue_tool(
if final_document_id:
try:
from app.services.jira import JiraKBSyncService
+
kb_service = JiraKBSyncService(db_session)
kb_result = await kb_service.sync_after_update(
document_id=final_document_id,
@@ -179,12 +211,18 @@ def create_update_jira_issue_tool(
search_space_id=search_space_id,
)
if kb_result["status"] == "success":
- kb_message_suffix = " Your knowledge base has also been updated."
+ kb_message_suffix = (
+ " Your knowledge base has also been updated."
+ )
else:
- kb_message_suffix = " The knowledge base will be updated in the next sync."
+ kb_message_suffix = (
+ " The knowledge base will be updated in the next sync."
+ )
except Exception as kb_err:
logger.warning(f"KB sync after update failed: {kb_err}")
- kb_message_suffix = " The knowledge base will be updated in the next sync."
+ kb_message_suffix = (
+ " The knowledge base will be updated in the next sync."
+ )
return {
"status": "success",
@@ -194,9 +232,13 @@ def create_update_jira_issue_tool(
except Exception as e:
from langgraph.errors import GraphInterrupt
+
if isinstance(e, GraphInterrupt):
raise
logger.error(f"Error updating Jira issue: {e}", exc_info=True)
- return {"status": "error", "message": "Something went wrong while updating the issue."}
+ return {
+ "status": "error",
+ "message": "Something went wrong while updating the issue.",
+ }
return update_jira_issue
diff --git a/surfsense_backend/app/agents/new_chat/tools/knowledge_base.py b/surfsense_backend/app/agents/new_chat/tools/knowledge_base.py
index 57f9bb124..12b8d5749 100644
--- a/surfsense_backend/app/agents/new_chat/tools/knowledge_base.py
+++ b/surfsense_backend/app/agents/new_chat/tools/knowledge_base.py
@@ -43,6 +43,7 @@ _DEGENERATE_QUERY_RE = re.compile(
# a real search. We want breadth (many docs) over depth (many chunks).
_BROWSE_MAX_CHUNKS_PER_DOC = 5
+
def _is_degenerate_query(query: str) -> bool:
"""Return True when the query carries no meaningful search signal.
@@ -82,7 +83,9 @@ async def _browse_recent_documents(
base_conditions = [Document.search_space_id == search_space_id]
if document_type is not None:
- type_list = document_type if isinstance(document_type, list) else [document_type]
+ type_list = (
+ document_type if isinstance(document_type, list) else [document_type]
+ )
doc_type_enums = []
for dt in type_list:
if isinstance(dt, str):
diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py
index 13a930b03..5bb0c52d1 100644
--- a/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py
+++ b/surfsense_backend/app/agents/new_chat/tools/notion/create_page.py
@@ -245,7 +245,9 @@ def create_create_notion_page_tool(
user_id=user_id,
)
if kb_result["status"] == "success":
- kb_message_suffix = " Your knowledge base has also been updated."
+ kb_message_suffix = (
+ " Your knowledge base has also been updated."
+ )
else:
kb_message_suffix = " This page will be added to your knowledge base in the next scheduled sync."
except Exception as kb_err:
diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py
index 91c31519a..fbb7c5004 100644
--- a/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py
+++ b/surfsense_backend/app/agents/new_chat/tools/notion/delete_page.py
@@ -280,7 +280,9 @@ def create_delete_notion_page_tool(
return {
"status": "auth_error",
"message": str(e),
- "connector_id": connector_id_from_context if "connector_id_from_context" in dir() else None,
+ "connector_id": connector_id_from_context
+ if "connector_id_from_context" in dir()
+ else None,
"connector_type": "notion",
}
if isinstance(e, ValueError | NotionAPIError):
diff --git a/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py b/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py
index 2d8d234fa..25f2b9918 100644
--- a/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py
+++ b/surfsense_backend/app/agents/new_chat/tools/notion/update_page.py
@@ -281,7 +281,9 @@ def create_update_notion_page_tool(
return {
"status": "auth_error",
"message": str(e),
- "connector_id": connector_id_from_context if "connector_id_from_context" in dir() else None,
+ "connector_id": connector_id_from_context
+ if "connector_id_from_context" in dir()
+ else None,
"connector_type": "notion",
}
if isinstance(e, ValueError | NotionAPIError):
diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py
index 6c6b12e3a..bba2f1f3a 100644
--- a/surfsense_backend/app/app.py
+++ b/surfsense_backend/app/app.py
@@ -341,7 +341,7 @@ if config.NEXT_FRONTEND_URL:
allowed_origins.append(www_url)
allowed_origins.extend(
- [ # For local development and desktop app
+ [ # For local development and desktop app
"http://localhost:3000",
"http://127.0.0.1:3000",
]
diff --git a/surfsense_backend/app/connectors/confluence_history.py b/surfsense_backend/app/connectors/confluence_history.py
index e9e547453..0dbf868c6 100644
--- a/surfsense_backend/app/connectors/confluence_history.py
+++ b/surfsense_backend/app/connectors/confluence_history.py
@@ -190,7 +190,9 @@ class ConfluenceHistoryConnector:
)
# Lazy import to avoid circular dependency
- from app.routes.confluence_add_connector_route import refresh_confluence_token
+ from app.routes.confluence_add_connector_route import (
+ refresh_confluence_token,
+ )
connector = await refresh_confluence_token(self._session, connector)
@@ -375,13 +377,9 @@ class ConfluenceHistoryConnector:
url, headers=headers, json=json_payload, params=params
)
elif method_upper == "DELETE":
- response = await http_client.delete(
- url, headers=headers, params=params
- )
+ response = await http_client.delete(url, headers=headers, params=params)
else:
- response = await http_client.get(
- url, headers=headers, params=params
- )
+ response = await http_client.get(url, headers=headers, params=params)
response.raise_for_status()
if response.status_code == 204 or not response.text:
diff --git a/surfsense_backend/app/connectors/google_calendar_connector.py b/surfsense_backend/app/connectors/google_calendar_connector.py
index 0350413e2..6150562c5 100644
--- a/surfsense_backend/app/connectors/google_calendar_connector.py
+++ b/surfsense_backend/app/connectors/google_calendar_connector.py
@@ -60,9 +60,7 @@ class GoogleCalendarConnector:
has_standard_refresh = bool(self._credentials.refresh_token)
if has_standard_refresh:
- if not all(
- [self._credentials.client_id, self._credentials.client_secret]
- ):
+ if not all([self._credentials.client_id, self._credentials.client_secret]):
raise ValueError(
"Google OAuth credentials (client_id, client_secret) must be set"
)
diff --git a/surfsense_backend/app/connectors/google_gmail_connector.py b/surfsense_backend/app/connectors/google_gmail_connector.py
index 0491aba62..d116fb5d6 100644
--- a/surfsense_backend/app/connectors/google_gmail_connector.py
+++ b/surfsense_backend/app/connectors/google_gmail_connector.py
@@ -89,9 +89,7 @@ class GoogleGmailConnector:
has_standard_refresh = bool(self._credentials.refresh_token)
if has_standard_refresh:
- if not all(
- [self._credentials.client_id, self._credentials.client_secret]
- ):
+ if not all([self._credentials.client_id, self._credentials.client_secret]):
raise ValueError(
"Google OAuth credentials (client_id, client_secret) must be set"
)
@@ -139,17 +137,13 @@ class GoogleGmailConnector:
from app.utils.oauth_security import TokenEncryption
creds_dict = json.loads(self._credentials.to_json())
- token_encrypted = connector.config.get(
- "_token_encrypted", False
- )
+ token_encrypted = connector.config.get("_token_encrypted", False)
if token_encrypted and config.SECRET_KEY:
token_encryption = TokenEncryption(config.SECRET_KEY)
if creds_dict.get("token"):
- creds_dict["token"] = (
- token_encryption.encrypt_token(
- creds_dict["token"]
- )
+ creds_dict["token"] = token_encryption.encrypt_token(
+ creds_dict["token"]
)
if creds_dict.get("refresh_token"):
creds_dict["refresh_token"] = (
diff --git a/surfsense_backend/app/retriever/chunks_hybrid_search.py b/surfsense_backend/app/retriever/chunks_hybrid_search.py
index e37d140d8..daac2069b 100644
--- a/surfsense_backend/app/retriever/chunks_hybrid_search.py
+++ b/surfsense_backend/app/retriever/chunks_hybrid_search.py
@@ -219,7 +219,9 @@ class ChucksHybridSearchRetriever:
# Add document type filter if provided (single string or list of strings)
if document_type is not None:
- type_list = document_type if isinstance(document_type, list) else [document_type]
+ type_list = (
+ document_type if isinstance(document_type, list) else [document_type]
+ )
doc_type_enums = []
for dt in type_list:
if isinstance(dt, str):
diff --git a/surfsense_backend/app/retriever/documents_hybrid_search.py b/surfsense_backend/app/retriever/documents_hybrid_search.py
index ff2a50db7..12fc55659 100644
--- a/surfsense_backend/app/retriever/documents_hybrid_search.py
+++ b/surfsense_backend/app/retriever/documents_hybrid_search.py
@@ -199,7 +199,9 @@ class DocumentHybridSearchRetriever:
# Add document type filter if provided (single string or list of strings)
if document_type is not None:
- type_list = document_type if isinstance(document_type, list) else [document_type]
+ type_list = (
+ document_type if isinstance(document_type, list) else [document_type]
+ )
doc_type_enums = []
for dt in type_list:
if isinstance(dt, str):
diff --git a/surfsense_backend/app/routes/composio_routes.py b/surfsense_backend/app/routes/composio_routes.py
index cbcb96853..4bf360365 100644
--- a/surfsense_backend/app/routes/composio_routes.py
+++ b/surfsense_backend/app/routes/composio_routes.py
@@ -461,10 +461,14 @@ async def reauth_composio_connector(
return_url: Optional frontend path to redirect to after completion
"""
if not ComposioService.is_enabled():
- raise HTTPException(status_code=503, detail="Composio integration is not enabled.")
+ raise HTTPException(
+ status_code=503, detail="Composio integration is not enabled."
+ )
if not config.SECRET_KEY:
- raise HTTPException(status_code=500, detail="SECRET_KEY not configured for OAuth security.")
+ raise HTTPException(
+ status_code=500, detail="SECRET_KEY not configured for OAuth security."
+ )
try:
result = await session.execute(
@@ -502,7 +506,9 @@ async def reauth_composio_connector(
callback_base = config.COMPOSIO_REDIRECT_URI
if not callback_base:
backend_url = config.BACKEND_URL or "http://localhost:8000"
- callback_base = f"{backend_url}/api/v1/auth/composio/connector/reauth/callback"
+ callback_base = (
+ f"{backend_url}/api/v1/auth/composio/connector/reauth/callback"
+ )
else:
# Replace the normal callback path with the reauth one
callback_base = callback_base.replace(
@@ -524,8 +530,13 @@ async def reauth_composio_connector(
connector.config = {**connector.config, "auth_expired": False}
flag_modified(connector, "config")
await session.commit()
- logger.info(f"Composio account {connected_account_id} refreshed server-side (no redirect needed)")
- return {"success": True, "message": "Authentication refreshed successfully."}
+ logger.info(
+ f"Composio account {connected_account_id} refreshed server-side (no redirect needed)"
+ )
+ return {
+ "success": True,
+ "message": "Authentication refreshed successfully.",
+ }
logger.info(f"Initiating Composio re-auth for connector {connector_id}")
return {"auth_url": refresh_result["redirect_url"]}
@@ -679,9 +690,7 @@ async def list_composio_drive_folders(
)
credentials = build_composio_credentials(composio_connected_account_id)
- drive_client = GoogleDriveClient(
- session, connector_id, credentials=credentials
- )
+ drive_client = GoogleDriveClient(session, connector_id, credentials=credentials)
items, error = await list_folder_contents(drive_client, parent_id=parent_id)
@@ -699,11 +708,17 @@ async def list_composio_drive_folders(
connector.config = {**connector.config, "auth_expired": True}
flag_modified(connector, "config")
await session.commit()
- logger.info(f"Marked Composio connector {connector_id} as auth_expired")
+ logger.info(
+ f"Marked Composio connector {connector_id} as auth_expired"
+ )
except Exception:
- logger.warning(f"Failed to persist auth_expired for connector {connector_id}", exc_info=True)
+ logger.warning(
+ f"Failed to persist auth_expired for connector {connector_id}",
+ exc_info=True,
+ )
raise HTTPException(
- status_code=400, detail="Google Drive authentication expired. Please re-authenticate."
+ status_code=400,
+ detail="Google Drive authentication expired. Please re-authenticate.",
)
raise HTTPException(
status_code=500, detail=f"Failed to list folder contents: {error}"
@@ -736,11 +751,17 @@ async def list_composio_drive_folders(
connector.config = {**connector.config, "auth_expired": True}
flag_modified(connector, "config")
await session.commit()
- logger.info(f"Marked Composio connector {connector_id} as auth_expired")
+ logger.info(
+ f"Marked Composio connector {connector_id} as auth_expired"
+ )
except Exception:
- logger.warning(f"Failed to persist auth_expired for connector {connector_id}", exc_info=True)
+ logger.warning(
+ f"Failed to persist auth_expired for connector {connector_id}",
+ exc_info=True,
+ )
raise HTTPException(
- status_code=400, detail="Google Drive authentication expired. Please re-authenticate."
+ status_code=400,
+ detail="Google Drive authentication expired. Please re-authenticate.",
) from e
raise HTTPException(
status_code=500, detail=f"Failed to list Drive contents: {e!s}"
diff --git a/surfsense_backend/app/routes/google_drive_add_connector_route.py b/surfsense_backend/app/routes/google_drive_add_connector_route.py
index 2fef58fec..1c9391610 100644
--- a/surfsense_backend/app/routes/google_drive_add_connector_route.py
+++ b/surfsense_backend/app/routes/google_drive_add_connector_route.py
@@ -520,9 +520,13 @@ async def list_google_drive_folders(
await session.commit()
logger.info(f"Marked connector {connector_id} as auth_expired")
except Exception:
- logger.warning(f"Failed to persist auth_expired for connector {connector_id}", exc_info=True)
+ logger.warning(
+ f"Failed to persist auth_expired for connector {connector_id}",
+ exc_info=True,
+ )
raise HTTPException(
- status_code=400, detail="Google Drive authentication expired. Please re-authenticate."
+ status_code=400,
+ detail="Google Drive authentication expired. Please re-authenticate.",
)
raise HTTPException(
status_code=500, detail=f"Failed to list folder contents: {error}"
@@ -562,9 +566,13 @@ async def list_google_drive_folders(
await session.commit()
logger.info(f"Marked connector {connector_id} as auth_expired")
except Exception:
- logger.warning(f"Failed to persist auth_expired for connector {connector_id}", exc_info=True)
+ logger.warning(
+ f"Failed to persist auth_expired for connector {connector_id}",
+ exc_info=True,
+ )
raise HTTPException(
- status_code=400, detail="Google Drive authentication expired. Please re-authenticate."
+ status_code=400,
+ detail="Google Drive authentication expired. Please re-authenticate.",
) from e
raise HTTPException(
status_code=500, detail=f"Failed to list Drive contents: {e!s}"
diff --git a/surfsense_backend/app/routes/linear_add_connector_route.py b/surfsense_backend/app/routes/linear_add_connector_route.py
index b5974c83b..310e3736a 100644
--- a/surfsense_backend/app/routes/linear_add_connector_route.py
+++ b/surfsense_backend/app/routes/linear_add_connector_route.py
@@ -580,7 +580,9 @@ async def refresh_linear_token(
credentials_dict = credentials.to_dict()
credentials_dict["_token_encrypted"] = True
if connector.config.get("organization_name"):
- credentials_dict["organization_name"] = connector.config["organization_name"]
+ credentials_dict["organization_name"] = connector.config[
+ "organization_name"
+ ]
credentials_dict.pop("auth_expired", None)
connector.config = credentials_dict
flag_modified(connector, "config")
diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py
index e6ed007d7..769186867 100644
--- a/surfsense_backend/app/routes/search_source_connectors_routes.py
+++ b/surfsense_backend/app/routes/search_source_connectors_routes.py
@@ -2374,7 +2374,11 @@ async def run_google_drive_indexing(
# Index each folder with indexing options
for folder in items.folders:
try:
- indexed_count, skipped_count, error_message = await index_google_drive_files(
+ (
+ indexed_count,
+ skipped_count,
+ error_message,
+ ) = await index_google_drive_files(
session,
connector_id,
search_space_id,
@@ -2429,7 +2433,9 @@ async def run_google_drive_indexing(
)
if _is_auth_error(error_message):
await _persist_auth_expired(session, connector_id)
- error_message = "Google Drive authentication expired. Please re-authenticate."
+ error_message = (
+ "Google Drive authentication expired. Please re-authenticate."
+ )
else:
# Update notification to storing stage
if notification:
diff --git a/surfsense_backend/app/services/composio_service.py b/surfsense_backend/app/services/composio_service.py
index 5f4f39b97..13fe37832 100644
--- a/surfsense_backend/app/services/composio_service.py
+++ b/surfsense_backend/app/services/composio_service.py
@@ -283,9 +283,7 @@ class ComposioService:
timeout=timeout,
)
status = getattr(account, "status", "UNKNOWN")
- logger.info(
- f"Composio account {connected_account_id} is now {status}"
- )
+ logger.info(f"Composio account {connected_account_id} is now {status}")
return status
except Exception as e:
logger.error(
diff --git a/surfsense_backend/app/services/confluence/kb_sync_service.py b/surfsense_backend/app/services/confluence/kb_sync_service.py
index a63a22f3f..f786a9920 100644
--- a/surfsense_backend/app/services/confluence/kb_sync_service.py
+++ b/surfsense_backend/app/services/confluence/kb_sync_service.py
@@ -67,7 +67,10 @@ class ConfluenceKBSyncService:
content_hash = unique_hash
user_llm = await get_user_long_context_llm(
- self.db_session, user_id, search_space_id, disable_streaming=True,
+ self.db_session,
+ user_id,
+ search_space_id,
+ disable_streaming=True,
)
doc_metadata_for_summary = {
@@ -116,17 +119,26 @@ class ConfluenceKBSyncService:
logger.info(
"KB sync after create succeeded: doc_id=%s, page=%s",
- document.id, page_title,
+ document.id,
+ page_title,
)
return {"status": "success"}
except Exception as e:
error_str = str(e).lower()
- if "duplicate key value violates unique constraint" in error_str or "uniqueviolationerror" in error_str:
+ if (
+ "duplicate key value violates unique constraint" in error_str
+ or "uniqueviolationerror" in error_str
+ ):
await self.db_session.rollback()
return {"status": "error", "message": "Duplicate document detected"}
- logger.error("KB sync after create failed for page %s: %s", page_title, e, exc_info=True)
+ logger.error(
+ "KB sync after create failed for page %s: %s",
+ page_title,
+ e,
+ exc_info=True,
+ )
await self.db_session.rollback()
return {"status": "error", "message": str(e)}
@@ -215,11 +227,14 @@ class ConfluenceKBSyncService:
logger.info(
"KB sync successful for document %s (%s)",
- document_id, page_title,
+ document_id,
+ page_title,
)
return {"status": "success"}
except Exception as e:
- logger.error("KB sync failed for document %s: %s", document_id, e, exc_info=True)
+ logger.error(
+ "KB sync failed for document %s: %s", document_id, e, exc_info=True
+ )
await self.db_session.rollback()
return {"status": "error", "message": str(e)}
diff --git a/surfsense_backend/app/services/confluence/tool_metadata_service.py b/surfsense_backend/app/services/confluence/tool_metadata_service.py
index 31b6d68f1..a66725bc6 100644
--- a/surfsense_backend/app/services/confluence/tool_metadata_service.py
+++ b/surfsense_backend/app/services/confluence/tool_metadata_service.py
@@ -83,7 +83,7 @@ class ConfluenceToolMetadataService:
async def _check_account_health(self, connector: SearchSourceConnector) -> bool:
"""Check if the Confluence connector auth is still valid.
-
+
Returns True if auth is expired/invalid, False if healthy.
"""
try:
@@ -112,7 +112,7 @@ class ConfluenceToolMetadataService:
async def get_creation_context(self, search_space_id: int, user_id: str) -> dict:
"""Return context needed to create a new Confluence page.
-
+
Fetches all connected accounts, and for the first healthy one fetches spaces.
"""
connectors = await self._get_all_confluence_connectors(search_space_id, user_id)
@@ -126,10 +126,12 @@ class ConfluenceToolMetadataService:
for connector in connectors:
auth_expired = await self._check_account_health(connector)
workspace = ConfluenceWorkspace.from_connector(connector)
- accounts.append({
- **workspace.to_dict(),
- "auth_expired": auth_expired,
- })
+ accounts.append(
+ {
+ **workspace.to_dict(),
+ "auth_expired": auth_expired,
+ }
+ )
if not auth_expired and not fetched_context:
try:
@@ -146,7 +148,8 @@ class ConfluenceToolMetadataService:
except Exception as e:
logger.warning(
"Failed to fetch Confluence spaces for connector %s: %s",
- connector.id, e,
+ connector.id,
+ e,
)
return {
@@ -158,7 +161,7 @@ class ConfluenceToolMetadataService:
self, search_space_id: int, user_id: str, page_ref: str
) -> dict:
"""Return context needed to update an indexed Confluence page.
-
+
Resolves the page from KB, then fetches current content and version from API.
"""
document = await self._resolve_page(search_space_id, user_id, page_ref)
@@ -191,7 +194,11 @@ class ConfluenceToolMetadataService:
await client.close()
except Exception as e:
error_str = str(e).lower()
- if "401" in error_str or "403" in error_str or "authentication" in error_str:
+ if (
+ "401" in error_str
+ or "403" in error_str
+ or "authentication" in error_str
+ ):
return {
"error": f"Failed to fetch Confluence page: {e!s}",
"auth_expired": True,
@@ -207,7 +214,9 @@ class ConfluenceToolMetadataService:
body_storage = storage.get("value", "")
version_obj = page_data.get("version", {})
- version_number = version_obj.get("number", 1) if isinstance(version_obj, dict) else 1
+ version_number = (
+ version_obj.get("number", 1) if isinstance(version_obj, dict) else 1
+ )
return {
"account": {**workspace.to_dict(), "auth_expired": False},
@@ -263,9 +272,7 @@ class ConfluenceToolMetadataService:
Document.document_type == DocumentType.CONFLUENCE_CONNECTOR,
SearchSourceConnector.user_id == user_id,
or_(
- func.lower(
- Document.document_metadata.op("->>")("page_title")
- )
+ func.lower(Document.document_metadata.op("->>")("page_title"))
== ref_lower,
func.lower(Document.title) == ref_lower,
),
diff --git a/surfsense_backend/app/services/gmail/tool_metadata_service.py b/surfsense_backend/app/services/gmail/tool_metadata_service.py
index 3292ae2f1..524e682cf 100644
--- a/surfsense_backend/app/services/gmail/tool_metadata_service.py
+++ b/surfsense_backend/app/services/gmail/tool_metadata_service.py
@@ -183,10 +183,12 @@ class GmailToolMetadataService:
and_(
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type.in_([
- SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
- SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
- ]),
+ SearchSourceConnector.connector_type.in_(
+ [
+ SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
+ SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR,
+ ]
+ ),
)
)
.order_by(SearchSourceConnector.last_indexed_at.desc())
@@ -223,9 +225,7 @@ class GmailToolMetadataService:
service = build("gmail", "v1", credentials=creds)
profile = await asyncio.get_event_loop().run_in_executor(
None,
- lambda: service.users()
- .getProfile(userId="me")
- .execute(),
+ lambda: service.users().getProfile(userId="me").execute(),
)
acc_dict["email"] = profile.get("emailAddress", "")
except Exception:
@@ -306,10 +306,12 @@ class GmailToolMetadataService:
draft = await asyncio.get_event_loop().run_in_executor(
None,
- lambda: service.users()
- .drafts()
- .get(userId="me", id=draft_id, format="full")
- .execute(),
+ lambda: (
+ service.users()
+ .drafts()
+ .get(userId="me", id=draft_id, format="full")
+ .execute()
+ ),
)
payload = draft.get("message", {}).get("payload", {})
@@ -422,15 +424,15 @@ class GmailToolMetadataService:
.filter(
and_(
Document.search_space_id == search_space_id,
- Document.document_type.in_([
- DocumentType.GOOGLE_GMAIL_CONNECTOR,
- DocumentType.COMPOSIO_GMAIL_CONNECTOR,
- ]),
+ Document.document_type.in_(
+ [
+ DocumentType.GOOGLE_GMAIL_CONNECTOR,
+ DocumentType.COMPOSIO_GMAIL_CONNECTOR,
+ ]
+ ),
SearchSourceConnector.user_id == user_id,
or_(
- func.lower(
- cast(Document.document_metadata["subject"], String)
- )
+ func.lower(cast(Document.document_metadata["subject"], String))
== func.lower(email_ref),
func.lower(Document.title) == func.lower(email_ref),
),
diff --git a/surfsense_backend/app/services/google_calendar/kb_sync_service.py b/surfsense_backend/app/services/google_calendar/kb_sync_service.py
index 1fe6bef7f..59afa116e 100644
--- a/surfsense_backend/app/services/google_calendar/kb_sync_service.py
+++ b/surfsense_backend/app/services/google_calendar/kb_sync_service.py
@@ -8,7 +8,12 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm.attributes import flag_modified
-from app.db import Document, DocumentType, SearchSourceConnector, SearchSourceConnectorType
+from app.db import (
+ Document,
+ DocumentType,
+ SearchSourceConnector,
+ SearchSourceConnectorType,
+)
from app.services.llm_service import get_user_long_context_llm
from app.utils.document_converters import (
create_document_chunks,
@@ -107,7 +112,9 @@ class GoogleCalendarKBSyncService:
)
else:
logger.warning("No LLM configured -- using fallback summary")
- summary_content = f"Google Calendar Event: {event_summary}\n\n{indexable_content}"
+ summary_content = (
+ f"Google Calendar Event: {event_summary}\n\n{indexable_content}"
+ )
summary_embedding = embed_text(summary_content)
chunks = await create_document_chunks(indexable_content)
@@ -201,12 +208,16 @@ class GoogleCalendarKBSyncService:
None, lambda: build("calendar", "v3", credentials=creds)
)
- calendar_id = (document.document_metadata or {}).get("calendar_id", "primary")
+ calendar_id = (document.document_metadata or {}).get(
+ "calendar_id", "primary"
+ )
live_event = await loop.run_in_executor(
None,
- lambda: service.events()
- .get(calendarId=calendar_id, eventId=event_id)
- .execute(),
+ lambda: (
+ service.events()
+ .get(calendarId=calendar_id, eventId=event_id)
+ .execute()
+ ),
)
event_summary = live_event.get("summary", "")
@@ -220,7 +231,10 @@ class GoogleCalendarKBSyncService:
end_time = end_data.get("dateTime", end_data.get("date", ""))
attendees = [
- {"email": a.get("email", ""), "responseStatus": a.get("responseStatus", "")}
+ {
+ "email": a.get("email", ""),
+ "responseStatus": a.get("responseStatus", ""),
+ }
for a in live_event.get("attendees", [])
]
@@ -252,7 +266,9 @@ class GoogleCalendarKBSyncService:
indexable_content, user_llm, doc_metadata_for_summary
)
else:
- summary_content = f"Google Calendar Event: {event_summary}\n\n{indexable_content}"
+ summary_content = (
+ f"Google Calendar Event: {event_summary}\n\n{indexable_content}"
+ )
summary_embedding = embed_text(summary_content)
chunks = await create_document_chunks(indexable_content)
@@ -313,7 +329,10 @@ class GoogleCalendarKBSyncService:
if not connector:
raise ValueError(f"Connector {connector_id} not found")
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
+ ):
cca_id = connector.config.get("composio_connected_account_id")
if cca_id:
return build_composio_credentials(cca_id)
@@ -328,11 +347,17 @@ class GoogleCalendarKBSyncService:
if token_encrypted and app_config.SECRET_KEY:
token_encryption = TokenEncryption(app_config.SECRET_KEY)
if config_data.get("token"):
- config_data["token"] = token_encryption.decrypt_token(config_data["token"])
+ config_data["token"] = token_encryption.decrypt_token(
+ config_data["token"]
+ )
if config_data.get("refresh_token"):
- config_data["refresh_token"] = token_encryption.decrypt_token(config_data["refresh_token"])
+ config_data["refresh_token"] = token_encryption.decrypt_token(
+ config_data["refresh_token"]
+ )
if config_data.get("client_secret"):
- config_data["client_secret"] = token_encryption.decrypt_token(config_data["client_secret"])
+ config_data["client_secret"] = token_encryption.decrypt_token(
+ config_data["client_secret"]
+ )
exp = config_data.get("expiry", "")
if exp:
diff --git a/surfsense_backend/app/services/google_calendar/tool_metadata_service.py b/surfsense_backend/app/services/google_calendar/tool_metadata_service.py
index d25736d1d..c7bfe1d50 100644
--- a/surfsense_backend/app/services/google_calendar/tool_metadata_service.py
+++ b/surfsense_backend/app/services/google_calendar/tool_metadata_service.py
@@ -37,7 +37,9 @@ class GoogleCalendarAccount:
name: str
@classmethod
- def from_connector(cls, connector: SearchSourceConnector) -> "GoogleCalendarAccount":
+ def from_connector(
+ cls, connector: SearchSourceConnector
+ ) -> "GoogleCalendarAccount":
return cls(id=connector.id, name=connector.name)
def to_dict(self) -> dict:
@@ -93,7 +95,10 @@ class GoogleCalendarToolMetadataService:
self._db_session = db_session
async def _build_credentials(self, connector: SearchSourceConnector) -> Credentials:
- if connector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
+ if (
+ connector.connector_type
+ == SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
+ ):
cca_id = connector.config.get("composio_connected_account_id")
if cca_id:
return build_composio_credentials(cca_id)
@@ -108,11 +113,17 @@ class GoogleCalendarToolMetadataService:
if token_encrypted and app_config.SECRET_KEY:
token_encryption = TokenEncryption(app_config.SECRET_KEY)
if config_data.get("token"):
- config_data["token"] = token_encryption.decrypt_token(config_data["token"])
+ config_data["token"] = token_encryption.decrypt_token(
+ config_data["token"]
+ )
if config_data.get("refresh_token"):
- config_data["refresh_token"] = token_encryption.decrypt_token(config_data["refresh_token"])
+ config_data["refresh_token"] = token_encryption.decrypt_token(
+ config_data["refresh_token"]
+ )
if config_data.get("client_secret"):
- config_data["client_secret"] = token_encryption.decrypt_token(config_data["client_secret"])
+ config_data["client_secret"] = token_encryption.decrypt_token(
+ config_data["client_secret"]
+ )
exp = config_data.get("expiry", "")
if exp:
@@ -149,10 +160,12 @@ class GoogleCalendarToolMetadataService:
loop = asyncio.get_event_loop()
await loop.run_in_executor(
None,
- lambda: build("calendar", "v3", credentials=creds)
- .calendarList()
- .list(maxResults=1)
- .execute(),
+ lambda: (
+ build("calendar", "v3", credentials=creds)
+ .calendarList()
+ .list(maxResults=1)
+ .execute()
+ ),
)
return False
except Exception as e:
@@ -252,11 +265,13 @@ class GoogleCalendarToolMetadataService:
None, lambda: service.calendarList().list().execute()
)
for cal in cal_list.get("items", []):
- calendars.append({
- "id": cal.get("id", ""),
- "summary": cal.get("summary", ""),
- "primary": cal.get("primary", False),
- })
+ calendars.append(
+ {
+ "id": cal.get("id", ""),
+ "summary": cal.get("summary", ""),
+ "primary": cal.get("primary", False),
+ }
+ )
tz_setting = await loop.run_in_executor(
None,
@@ -314,23 +329,34 @@ class GoogleCalendarToolMetadataService:
calendar_id = event.calendar_id or "primary"
live_event = await loop.run_in_executor(
None,
- lambda: service.events()
- .get(calendarId=calendar_id, eventId=event.event_id)
- .execute(),
+ lambda: (
+ service.events()
+ .get(calendarId=calendar_id, eventId=event.event_id)
+ .execute()
+ ),
)
event_dict["summary"] = live_event.get("summary", event_dict["summary"])
- event_dict["description"] = live_event.get("description", event_dict["description"])
+ event_dict["description"] = live_event.get(
+ "description", event_dict["description"]
+ )
event_dict["location"] = live_event.get("location", event_dict["location"])
start_data = live_event.get("start", {})
- event_dict["start"] = start_data.get("dateTime", start_data.get("date", event_dict["start"]))
+ event_dict["start"] = start_data.get(
+ "dateTime", start_data.get("date", event_dict["start"])
+ )
end_data = live_event.get("end", {})
- event_dict["end"] = end_data.get("dateTime", end_data.get("date", event_dict["end"]))
+ event_dict["end"] = end_data.get(
+ "dateTime", end_data.get("date", event_dict["end"])
+ )
event_dict["attendees"] = [
- {"email": a.get("email", ""), "responseStatus": a.get("responseStatus", "")}
+ {
+ "email": a.get("email", ""),
+ "responseStatus": a.get("responseStatus", ""),
+ }
for a in live_event.get("attendees", [])
]
except Exception:
diff --git a/surfsense_backend/app/services/google_drive/kb_sync_service.py b/surfsense_backend/app/services/google_drive/kb_sync_service.py
index 2bcd69e0c..92a39f7b9 100644
--- a/surfsense_backend/app/services/google_drive/kb_sync_service.py
+++ b/surfsense_backend/app/services/google_drive/kb_sync_service.py
@@ -56,7 +56,9 @@ class GoogleDriveKBSyncService:
indexable_content = (content or "").strip()
if not indexable_content:
- indexable_content = f"Google Drive file: {file_name} (type: {mime_type})"
+ indexable_content = (
+ f"Google Drive file: {file_name} (type: {mime_type})"
+ )
content_hash = generate_content_hash(indexable_content, search_space_id)
@@ -93,7 +95,9 @@ class GoogleDriveKBSyncService:
)
else:
logger.warning("No LLM configured — using fallback summary")
- summary_content = f"Google Drive File: {file_name}\n\n{indexable_content}"
+ summary_content = (
+ f"Google Drive File: {file_name}\n\n{indexable_content}"
+ )
summary_embedding = embed_text(summary_content)
chunks = await create_document_chunks(indexable_content)
diff --git a/surfsense_backend/app/services/google_drive/tool_metadata_service.py b/surfsense_backend/app/services/google_drive/tool_metadata_service.py
index f118c2c7a..221bee14a 100644
--- a/surfsense_backend/app/services/google_drive/tool_metadata_service.py
+++ b/surfsense_backend/app/services/google_drive/tool_metadata_service.py
@@ -133,10 +133,12 @@ class GoogleDriveToolMetadataService:
and_(
SearchSourceConnector.id == document.connector_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type.in_([
- SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
- SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
- ]),
+ SearchSourceConnector.connector_type.in_(
+ [
+ SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
+ SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
+ ]
+ ),
)
)
)
@@ -168,10 +170,12 @@ class GoogleDriveToolMetadataService:
and_(
SearchSourceConnector.search_space_id == search_space_id,
SearchSourceConnector.user_id == user_id,
- SearchSourceConnector.connector_type.in_([
- SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
- SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
- ]),
+ SearchSourceConnector.connector_type.in_(
+ [
+ SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR,
+ SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
+ ]
+ ),
)
)
.order_by(SearchSourceConnector.last_indexed_at.desc())
diff --git a/surfsense_backend/app/services/jira/kb_sync_service.py b/surfsense_backend/app/services/jira/kb_sync_service.py
index 8d7fd6bb4..4d2a66e52 100644
--- a/surfsense_backend/app/services/jira/kb_sync_service.py
+++ b/surfsense_backend/app/services/jira/kb_sync_service.py
@@ -53,7 +53,8 @@ class JiraKBSyncService:
if existing:
logger.info(
"Document for Jira issue %s already exists (doc_id=%s), skipping",
- issue_identifier, existing.id,
+ issue_identifier,
+ existing.id,
)
return {"status": "success"}
@@ -61,7 +62,9 @@ class JiraKBSyncService:
if not indexable_content:
indexable_content = f"Jira Issue {issue_identifier}: {issue_title}"
- issue_content = f"# {issue_identifier}: {issue_title}\n\n{indexable_content}"
+ issue_content = (
+ f"# {issue_identifier}: {issue_title}\n\n{indexable_content}"
+ )
content_hash = generate_content_hash(issue_content, search_space_id)
@@ -73,7 +76,10 @@ class JiraKBSyncService:
content_hash = unique_hash
user_llm = await get_user_long_context_llm(
- self.db_session, user_id, search_space_id, disable_streaming=True,
+ self.db_session,
+ user_id,
+ search_space_id,
+ disable_streaming=True,
)
doc_metadata_for_summary = {
@@ -88,7 +94,9 @@ class JiraKBSyncService:
issue_content, user_llm, doc_metadata_for_summary
)
else:
- summary_content = f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}"
+ summary_content = (
+ f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}"
+ )
summary_embedding = embed_text(summary_content)
chunks = await create_document_chunks(issue_content)
@@ -122,17 +130,26 @@ class JiraKBSyncService:
logger.info(
"KB sync after create succeeded: doc_id=%s, issue=%s",
- document.id, issue_identifier,
+ document.id,
+ issue_identifier,
)
return {"status": "success"}
except Exception as e:
error_str = str(e).lower()
- if "duplicate key value violates unique constraint" in error_str or "uniqueviolationerror" in error_str:
+ if (
+ "duplicate key value violates unique constraint" in error_str
+ or "uniqueviolationerror" in error_str
+ ):
await self.db_session.rollback()
return {"status": "error", "message": "Duplicate document detected"}
- logger.error("KB sync after create failed for issue %s: %s", issue_identifier, e, exc_info=True)
+ logger.error(
+ "KB sync after create failed for issue %s: %s",
+ issue_identifier,
+ e,
+ exc_info=True,
+ )
await self.db_session.rollback()
return {"status": "error", "message": str(e)}
@@ -189,14 +206,18 @@ class JiraKBSyncService:
issue_content, user_llm, doc_meta
)
else:
- summary_content = f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}"
+ summary_content = (
+ f"Jira Issue {issue_identifier}: {issue_title}\n\n{issue_content}"
+ )
summary_embedding = embed_text(summary_content)
chunks = await create_document_chunks(issue_content)
document.title = f"{issue_identifier}: {issue_title}"
document.content = summary_content
- document.content_hash = generate_content_hash(issue_content, search_space_id)
+ document.content_hash = generate_content_hash(
+ issue_content, search_space_id
+ )
document.embedding = summary_embedding
from sqlalchemy.orm.attributes import flag_modified
@@ -219,11 +240,15 @@ class JiraKBSyncService:
logger.info(
"KB sync successful for document %s (%s: %s)",
- document_id, issue_identifier, issue_title,
+ document_id,
+ issue_identifier,
+ issue_title,
)
return {"status": "success"}
except Exception as e:
- logger.error("KB sync failed for document %s: %s", document_id, e, exc_info=True)
+ logger.error(
+ "KB sync failed for document %s: %s", document_id, e, exc_info=True
+ )
await self.db_session.rollback()
return {"status": "error", "message": str(e)}
diff --git a/surfsense_backend/app/services/jira/tool_metadata_service.py b/surfsense_backend/app/services/jira/tool_metadata_service.py
index d8e7f2d3d..aaec89cd5 100644
--- a/surfsense_backend/app/services/jira/tool_metadata_service.py
+++ b/surfsense_backend/app/services/jira/tool_metadata_service.py
@@ -87,7 +87,7 @@ class JiraToolMetadataService:
async def _check_account_health(self, connector: SearchSourceConnector) -> bool:
"""Check if the Jira connector auth is still valid.
-
+
Returns True if auth is expired/invalid, False if healthy.
"""
try:
@@ -98,9 +98,7 @@ class JiraToolMetadataService:
await asyncio.to_thread(jira_client.get_myself)
return False
except Exception as e:
- logger.warning(
- "Jira connector %s health check failed: %s", connector.id, e
- )
+ logger.warning("Jira connector %s health check failed: %s", connector.id, e)
try:
connector.config = {**connector.config, "auth_expired": True}
flag_modified(connector, "config")
@@ -116,7 +114,7 @@ class JiraToolMetadataService:
async def get_creation_context(self, search_space_id: int, user_id: str) -> dict:
"""Return context needed to create a new Jira issue.
-
+
Fetches all connected Jira accounts, and for the first healthy one
fetches projects, issue types, and priorities.
"""
@@ -165,7 +163,8 @@ class JiraToolMetadataService:
except Exception as e:
logger.warning(
"Failed to fetch Jira context for connector %s: %s",
- connector.id, e,
+ connector.id,
+ e,
)
return {
@@ -179,7 +178,7 @@ class JiraToolMetadataService:
self, search_space_id: int, user_id: str, issue_ref: str
) -> dict:
"""Return context needed to update an indexed Jira issue.
-
+
Resolves the issue from the KB, then fetches current details from the Jira API.
"""
document = await self._resolve_issue(search_space_id, user_id, issue_ref)
@@ -209,13 +208,15 @@ class JiraToolMetadataService:
session=self._db_session, connector_id=connector.id
)
jira_client = await jira_history._get_jira_client()
- issue_data = await asyncio.to_thread(
- jira_client.get_issue, issue.issue_id
- )
+ issue_data = await asyncio.to_thread(jira_client.get_issue, issue.issue_id)
formatted = jira_client.format_issue(issue_data)
except Exception as e:
error_str = str(e).lower()
- if "401" in error_str or "403" in error_str or "authentication" in error_str:
+ if (
+ "401" in error_str
+ or "403" in error_str
+ or "authentication" in error_str
+ ):
return {
"error": f"Failed to fetch Jira issue: {e!s}",
"auth_expired": True,
diff --git a/surfsense_backend/app/services/linear/kb_sync_service.py b/surfsense_backend/app/services/linear/kb_sync_service.py
index 08d04d0d5..dab42af55 100644
--- a/surfsense_backend/app/services/linear/kb_sync_service.py
+++ b/surfsense_backend/app/services/linear/kb_sync_service.py
@@ -66,7 +66,9 @@ class LinearKBSyncService:
if not indexable_content:
indexable_content = f"Linear Issue {issue_identifier}: {issue_title}"
- issue_content = f"# {issue_identifier}: {issue_title}\n\n{indexable_content}"
+ issue_content = (
+ f"# {issue_identifier}: {issue_title}\n\n{indexable_content}"
+ )
content_hash = generate_content_hash(issue_content, search_space_id)
diff --git a/surfsense_backend/app/services/linear/tool_metadata_service.py b/surfsense_backend/app/services/linear/tool_metadata_service.py
index ebd4ead92..9848534b0 100644
--- a/surfsense_backend/app/services/linear/tool_metadata_service.py
+++ b/surfsense_backend/app/services/linear/tool_metadata_service.py
@@ -190,7 +190,11 @@ class LinearToolMetadataService:
issue_api = await self._fetch_issue_context(linear_client, issue.id)
except Exception as e:
error_str = str(e).lower()
- if "401" in error_str or "authentication" in error_str or "re-authenticate" in error_str:
+ if (
+ "401" in error_str
+ or "authentication" in error_str
+ or "re-authenticate" in error_str
+ ):
return {
"error": f"Failed to fetch Linear issue context: {e!s}",
"auth_expired": True,
diff --git a/surfsense_backend/app/services/notion/tool_metadata_service.py b/surfsense_backend/app/services/notion/tool_metadata_service.py
index f6e55bdab..097ef3461 100644
--- a/surfsense_backend/app/services/notion/tool_metadata_service.py
+++ b/surfsense_backend/app/services/notion/tool_metadata_service.py
@@ -102,7 +102,10 @@ class NotionToolMetadataService:
)
db_connector = result.scalar_one_or_none()
if db_connector and not db_connector.config.get("auth_expired"):
- db_connector.config = {**db_connector.config, "auth_expired": True}
+ db_connector.config = {
+ **db_connector.config,
+ "auth_expired": True,
+ }
flag_modified(db_connector, "config")
await self._db_session.commit()
await self._db_session.refresh(db_connector)
diff --git a/surfsense_backend/app/tasks/connector_indexers/google_calendar_indexer.py b/surfsense_backend/app/tasks/connector_indexers/google_calendar_indexer.py
index 2dc6fd18b..c558fb38a 100644
--- a/surfsense_backend/app/tasks/connector_indexers/google_calendar_indexer.py
+++ b/surfsense_backend/app/tasks/connector_indexers/google_calendar_indexer.py
@@ -114,9 +114,7 @@ async def index_google_calendar_events(
# Build credentials based on connector type
if connector.connector_type in COMPOSIO_GOOGLE_CONNECTOR_TYPES:
- connected_account_id = connector.config.get(
- "composio_connected_account_id"
- )
+ connected_account_id = connector.config.get("composio_connected_account_id")
if not connected_account_id:
await task_logger.log_task_failure(
log_entry,
@@ -396,10 +394,19 @@ async def index_google_calendar_events(
session, legacy_hash
)
if existing_document:
- existing_document.unique_identifier_hash = unique_identifier_hash
- if existing_document.document_type == DocumentType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR:
- existing_document.document_type = DocumentType.GOOGLE_CALENDAR_CONNECTOR
- logger.info(f"Migrated legacy Composio Calendar document: {event_id}")
+ existing_document.unique_identifier_hash = (
+ unique_identifier_hash
+ )
+ if (
+ existing_document.document_type
+ == DocumentType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
+ ):
+ existing_document.document_type = (
+ DocumentType.GOOGLE_CALENDAR_CONNECTOR
+ )
+ logger.info(
+ f"Migrated legacy Composio Calendar document: {event_id}"
+ )
if existing_document:
# Document exists - check if content has changed
diff --git a/surfsense_backend/app/tasks/connector_indexers/google_drive_indexer.py b/surfsense_backend/app/tasks/connector_indexers/google_drive_indexer.py
index a865bee28..260db0ce6 100644
--- a/surfsense_backend/app/tasks/connector_indexers/google_drive_indexer.py
+++ b/surfsense_backend/app/tasks/connector_indexers/google_drive_indexer.py
@@ -121,13 +121,13 @@ async def index_google_drive_files(
# Build credentials based on connector type
pre_built_credentials = None
if connector.connector_type in COMPOSIO_GOOGLE_CONNECTOR_TYPES:
- connected_account_id = connector.config.get(
- "composio_connected_account_id"
- )
+ connected_account_id = connector.config.get("composio_connected_account_id")
if not connected_account_id:
error_msg = f"Composio connected_account_id not found for connector {connector_id}"
await task_logger.log_task_failure(
- log_entry, error_msg, "Missing Composio account",
+ log_entry,
+ error_msg,
+ "Missing Composio account",
{"error_type": "MissingComposioAccount"},
)
return 0, 0, error_msg
@@ -355,13 +355,13 @@ async def index_google_drive_single_file(
pre_built_credentials = None
if connector.connector_type in COMPOSIO_GOOGLE_CONNECTOR_TYPES:
- connected_account_id = connector.config.get(
- "composio_connected_account_id"
- )
+ connected_account_id = connector.config.get("composio_connected_account_id")
if not connected_account_id:
error_msg = f"Composio connected_account_id not found for connector {connector_id}"
await task_logger.log_task_failure(
- log_entry, error_msg, "Missing Composio account",
+ log_entry,
+ error_msg,
+ "Missing Composio account",
{"error_type": "MissingComposioAccount"},
)
return 0, error_msg
@@ -611,7 +611,11 @@ async def _index_full_scan(
if not files_to_process and first_listing_error:
error_lower = first_listing_error.lower()
- if "401" in first_listing_error or "invalid credentials" in error_lower or "authError" in first_listing_error:
+ if (
+ "401" in first_listing_error
+ or "invalid credentials" in error_lower
+ or "authError" in first_listing_error
+ ):
raise Exception(
f"Google Drive authentication failed. Please re-authenticate. "
f"(Error: {first_listing_error})"
@@ -704,7 +708,11 @@ async def _index_with_delta_sync(
if error:
logger.error(f"Error fetching changes: {error}")
error_lower = error.lower()
- if "401" in error or "invalid credentials" in error_lower or "authError" in error:
+ if (
+ "401" in error
+ or "invalid credentials" in error_lower
+ or "authError" in error
+ ):
raise Exception(
f"Google Drive authentication failed. Please re-authenticate. "
f"(Error: {error})"
@@ -872,7 +880,10 @@ async def _create_pending_document_for_file(
)
if existing_document:
existing_document.unique_identifier_hash = unique_identifier_hash
- if existing_document.document_type == DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR:
+ if (
+ existing_document.document_type
+ == DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR
+ ):
existing_document.document_type = DocumentType.GOOGLE_DRIVE_FILE
logger.info(f"Migrated legacy Composio document to native type: {file_id}")
@@ -984,10 +995,12 @@ async def _check_rename_only_update(
result = await session.execute(
select(Document).where(
Document.search_space_id == search_space_id,
- Document.document_type.in_([
- DocumentType.GOOGLE_DRIVE_FILE,
- DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
- ]),
+ Document.document_type.in_(
+ [
+ DocumentType.GOOGLE_DRIVE_FILE,
+ DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
+ ]
+ ),
cast(Document.document_metadata["google_drive_file_id"], String)
== file_id,
)
@@ -1000,7 +1013,10 @@ async def _check_rename_only_update(
if existing_document:
if existing_document.unique_identifier_hash != primary_hash:
existing_document.unique_identifier_hash = primary_hash
- if existing_document.document_type == DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR:
+ if (
+ existing_document.document_type
+ == DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR
+ ):
existing_document.document_type = DocumentType.GOOGLE_DRIVE_FILE
logger.info(f"Migrated legacy Composio Drive document: {file_id}")
@@ -1232,10 +1248,12 @@ async def _remove_document(session: AsyncSession, file_id: str, search_space_id:
result = await session.execute(
select(Document).where(
Document.search_space_id == search_space_id,
- Document.document_type.in_([
- DocumentType.GOOGLE_DRIVE_FILE,
- DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
- ]),
+ Document.document_type.in_(
+ [
+ DocumentType.GOOGLE_DRIVE_FILE,
+ DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR,
+ ]
+ ),
cast(Document.document_metadata["google_drive_file_id"], String)
== file_id,
)
diff --git a/surfsense_backend/app/tasks/connector_indexers/google_gmail_indexer.py b/surfsense_backend/app/tasks/connector_indexers/google_gmail_indexer.py
index edd03ae02..e7e4a8615 100644
--- a/surfsense_backend/app/tasks/connector_indexers/google_gmail_indexer.py
+++ b/surfsense_backend/app/tasks/connector_indexers/google_gmail_indexer.py
@@ -119,9 +119,7 @@ async def index_google_gmail_messages(
# Build credentials based on connector type
if connector.connector_type in COMPOSIO_GOOGLE_CONNECTOR_TYPES:
- connected_account_id = connector.config.get(
- "composio_connected_account_id"
- )
+ connected_account_id = connector.config.get("composio_connected_account_id")
if not connected_account_id:
await task_logger.log_task_failure(
log_entry,
@@ -323,10 +321,19 @@ async def index_google_gmail_messages(
session, legacy_hash
)
if existing_document:
- existing_document.unique_identifier_hash = unique_identifier_hash
- if existing_document.document_type == DocumentType.COMPOSIO_GMAIL_CONNECTOR:
- existing_document.document_type = DocumentType.GOOGLE_GMAIL_CONNECTOR
- logger.info(f"Migrated legacy Composio Gmail document: {message_id}")
+ existing_document.unique_identifier_hash = (
+ unique_identifier_hash
+ )
+ if (
+ existing_document.document_type
+ == DocumentType.COMPOSIO_GMAIL_CONNECTOR
+ ):
+ existing_document.document_type = (
+ DocumentType.GOOGLE_GMAIL_CONNECTOR
+ )
+ logger.info(
+ f"Migrated legacy Composio Gmail document: {message_id}"
+ )
if existing_document:
# Document exists - check if content has changed
diff --git a/surfsense_backend/app/tasks/document_processors/file_processors.py b/surfsense_backend/app/tasks/document_processors/file_processors.py
index 78d9315e9..6c0ae1870 100644
--- a/surfsense_backend/app/tasks/document_processors/file_processors.py
+++ b/surfsense_backend/app/tasks/document_processors/file_processors.py
@@ -1270,9 +1270,16 @@ async def process_file_in_background(
print("Error deleting temp file", e)
pass
- enable_summary = connector.get("enable_summary", True) if connector else True
+ enable_summary = (
+ connector.get("enable_summary", True) if connector else True
+ )
result = await add_received_file_document_using_unstructured(
- session, filename, docs, search_space_id, user_id, connector,
+ session,
+ filename,
+ docs,
+ search_space_id,
+ user_id,
+ connector,
enable_summary=enable_summary,
)
@@ -1414,7 +1421,9 @@ async def process_file_in_background(
# Extract text content from the markdown documents
markdown_content = doc.text
- enable_summary = connector.get("enable_summary", True) if connector else True
+ enable_summary = (
+ connector.get("enable_summary", True) if connector else True
+ )
doc_result = await add_received_file_document_using_llamacloud(
session,
filename,
@@ -1569,7 +1578,9 @@ async def process_file_in_background(
session, notification, stage="chunking"
)
- enable_summary = connector.get("enable_summary", True) if connector else True
+ enable_summary = (
+ connector.get("enable_summary", True) if connector else True
+ )
doc_result = await add_received_file_document_using_docling(
session,
filename,
diff --git a/surfsense_backend/tests/integration/google_unification/conftest.py b/surfsense_backend/tests/integration/google_unification/conftest.py
index 768bab84b..4483cf7dd 100644
--- a/surfsense_backend/tests/integration/google_unification/conftest.py
+++ b/surfsense_backend/tests/integration/google_unification/conftest.py
@@ -156,9 +156,7 @@ async def committed_google_data(async_engine):
session.add(user)
await session.flush()
- space = SearchSpace(
- name=f"Google Test {uuid.uuid4().hex[:6]}", user_id=user.id
- )
+ space = SearchSpace(name=f"Google Test {uuid.uuid4().hex[:6]}", user_id=user.id)
session.add(space)
await session.flush()
space_id = space.id
@@ -215,7 +213,9 @@ async def committed_google_data(async_engine):
def patched_session_factory(async_engine, monkeypatch):
"""Replace ``async_session_maker`` in connector_service with one bound to the test engine."""
test_maker = async_sessionmaker(async_engine, expire_on_commit=False)
- monkeypatch.setattr("app.services.connector_service.async_session_maker", test_maker)
+ monkeypatch.setattr(
+ "app.services.connector_service.async_session_maker", test_maker
+ )
return test_maker
diff --git a/surfsense_backend/tests/integration/google_unification/test_calendar_indexer_credentials.py b/surfsense_backend/tests/integration/google_unification/test_calendar_indexer_credentials.py
index 7e4723dce..795f0d564 100644
--- a/surfsense_backend/tests/integration/google_unification/test_calendar_indexer_credentials.py
+++ b/surfsense_backend/tests/integration/google_unification/test_calendar_indexer_credentials.py
@@ -14,7 +14,12 @@ import pytest_asyncio
from app.db import SearchSourceConnectorType
-from .conftest import cleanup_space, make_session_factory, mock_task_logger, seed_connector
+from .conftest import (
+ cleanup_space,
+ make_session_factory,
+ mock_task_logger,
+ seed_connector,
+)
pytestmark = pytest.mark.integration
@@ -52,8 +57,10 @@ async def native_calendar(async_engine):
async_engine,
connector_type=SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR,
config={
- "token": "fake", "refresh_token": "fake",
- "client_id": "fake", "client_secret": "fake",
+ "token": "fake",
+ "refresh_token": "fake",
+ "client_id": "fake",
+ "client_secret": "fake",
"token_uri": "https://oauth2.googleapis.com/token",
},
name_prefix="cal-native",
@@ -66,10 +73,16 @@ async def native_calendar(async_engine):
@patch(f"{_INDEXER_MODULE}.GoogleCalendarConnector")
@patch(f"{_INDEXER_MODULE}.build_composio_credentials")
async def test_composio_calendar_uses_composio_credentials(
- mock_build_creds, mock_cal_cls, mock_tl_cls, async_engine, composio_calendar,
+ mock_build_creds,
+ mock_cal_cls,
+ mock_tl_cls,
+ async_engine,
+ composio_calendar,
):
"""Calendar indexer calls build_composio_credentials for a Composio connector."""
- from app.tasks.connector_indexers.google_calendar_indexer import index_google_calendar_events
+ from app.tasks.connector_indexers.google_calendar_indexer import (
+ index_google_calendar_events,
+ )
data = composio_calendar
mock_creds = MagicMock(name="composio-creds")
@@ -77,14 +90,18 @@ async def test_composio_calendar_uses_composio_credentials(
mock_tl_cls.return_value = mock_task_logger()
mock_cal_instance = MagicMock()
- mock_cal_instance.get_all_primary_calendar_events = AsyncMock(return_value=([], None))
+ mock_cal_instance.get_all_primary_calendar_events = AsyncMock(
+ return_value=([], None)
+ )
mock_cal_cls.return_value = mock_cal_instance
maker = make_session_factory(async_engine)
async with maker() as session:
await index_google_calendar_events(
- session=session, connector_id=data["connector_id"],
- search_space_id=data["search_space_id"], user_id=data["user_id"],
+ session=session,
+ connector_id=data["connector_id"],
+ search_space_id=data["search_space_id"],
+ user_id=data["user_id"],
)
mock_build_creds.assert_called_once_with(_COMPOSIO_ACCOUNT_ID)
@@ -96,10 +113,15 @@ async def test_composio_calendar_uses_composio_credentials(
@patch(f"{_INDEXER_MODULE}.TaskLoggingService")
@patch(f"{_INDEXER_MODULE}.build_composio_credentials")
async def test_composio_calendar_without_account_id_returns_error(
- mock_build_creds, mock_tl_cls, async_engine, composio_calendar_no_id,
+ mock_build_creds,
+ mock_tl_cls,
+ async_engine,
+ composio_calendar_no_id,
):
"""Calendar indexer returns error when Composio connector lacks connected_account_id."""
- from app.tasks.connector_indexers.google_calendar_indexer import index_google_calendar_events
+ from app.tasks.connector_indexers.google_calendar_indexer import (
+ index_google_calendar_events,
+ )
data = composio_calendar_no_id
mock_tl_cls.return_value = mock_task_logger()
@@ -107,8 +129,10 @@ async def test_composio_calendar_without_account_id_returns_error(
maker = make_session_factory(async_engine)
async with maker() as session:
count, _skipped, error = await index_google_calendar_events(
- session=session, connector_id=data["connector_id"],
- search_space_id=data["search_space_id"], user_id=data["user_id"],
+ session=session,
+ connector_id=data["connector_id"],
+ search_space_id=data["search_space_id"],
+ user_id=data["user_id"],
)
assert count == 0
@@ -121,23 +145,33 @@ async def test_composio_calendar_without_account_id_returns_error(
@patch(f"{_INDEXER_MODULE}.GoogleCalendarConnector")
@patch(f"{_INDEXER_MODULE}.build_composio_credentials")
async def test_native_calendar_does_not_use_composio_credentials(
- mock_build_creds, mock_cal_cls, mock_tl_cls, async_engine, native_calendar,
+ mock_build_creds,
+ mock_cal_cls,
+ mock_tl_cls,
+ async_engine,
+ native_calendar,
):
"""Calendar indexer does NOT call build_composio_credentials for a native connector."""
- from app.tasks.connector_indexers.google_calendar_indexer import index_google_calendar_events
+ from app.tasks.connector_indexers.google_calendar_indexer import (
+ index_google_calendar_events,
+ )
data = native_calendar
mock_tl_cls.return_value = mock_task_logger()
mock_cal_instance = MagicMock()
- mock_cal_instance.get_all_primary_calendar_events = AsyncMock(return_value=([], None))
+ mock_cal_instance.get_all_primary_calendar_events = AsyncMock(
+ return_value=([], None)
+ )
mock_cal_cls.return_value = mock_cal_instance
maker = make_session_factory(async_engine)
async with maker() as session:
await index_google_calendar_events(
- session=session, connector_id=data["connector_id"],
- search_space_id=data["search_space_id"], user_id=data["user_id"],
+ session=session,
+ connector_id=data["connector_id"],
+ search_space_id=data["search_space_id"],
+ user_id=data["user_id"],
)
mock_build_creds.assert_not_called()
diff --git a/surfsense_backend/tests/integration/google_unification/test_drive_indexer_credentials.py b/surfsense_backend/tests/integration/google_unification/test_drive_indexer_credentials.py
index b6ffa7936..f17fc69f5 100644
--- a/surfsense_backend/tests/integration/google_unification/test_drive_indexer_credentials.py
+++ b/surfsense_backend/tests/integration/google_unification/test_drive_indexer_credentials.py
@@ -14,7 +14,12 @@ import pytest_asyncio
from app.db import SearchSourceConnectorType
-from .conftest import cleanup_space, make_session_factory, mock_task_logger, seed_connector
+from .conftest import (
+ cleanup_space,
+ make_session_factory,
+ mock_task_logger,
+ seed_connector,
+)
pytestmark = pytest.mark.integration
@@ -129,7 +134,9 @@ async def test_composio_connector_without_account_id_returns_error(
assert count == 0
assert error is not None
- assert "composio_connected_account_id" in error.lower() or "composio" in error.lower()
+ assert (
+ "composio_connected_account_id" in error.lower() or "composio" in error.lower()
+ )
mock_build_creds.assert_not_called()
diff --git a/surfsense_backend/tests/integration/google_unification/test_gmail_indexer_credentials.py b/surfsense_backend/tests/integration/google_unification/test_gmail_indexer_credentials.py
index 4097b2e95..afb3e64c3 100644
--- a/surfsense_backend/tests/integration/google_unification/test_gmail_indexer_credentials.py
+++ b/surfsense_backend/tests/integration/google_unification/test_gmail_indexer_credentials.py
@@ -14,7 +14,12 @@ import pytest_asyncio
from app.db import SearchSourceConnectorType
-from .conftest import cleanup_space, make_session_factory, mock_task_logger, seed_connector
+from .conftest import (
+ cleanup_space,
+ make_session_factory,
+ mock_task_logger,
+ seed_connector,
+)
pytestmark = pytest.mark.integration
@@ -52,8 +57,10 @@ async def native_gmail(async_engine):
async_engine,
connector_type=SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR,
config={
- "token": "fake", "refresh_token": "fake",
- "client_id": "fake", "client_secret": "fake",
+ "token": "fake",
+ "refresh_token": "fake",
+ "client_id": "fake",
+ "client_secret": "fake",
"token_uri": "https://oauth2.googleapis.com/token",
},
name_prefix="gmail-native",
@@ -66,10 +73,16 @@ async def native_gmail(async_engine):
@patch(f"{_INDEXER_MODULE}.GoogleGmailConnector")
@patch(f"{_INDEXER_MODULE}.build_composio_credentials")
async def test_composio_gmail_uses_composio_credentials(
- mock_build_creds, mock_gmail_cls, mock_tl_cls, async_engine, composio_gmail,
+ mock_build_creds,
+ mock_gmail_cls,
+ mock_tl_cls,
+ async_engine,
+ composio_gmail,
):
"""Gmail indexer calls build_composio_credentials for a Composio connector."""
- from app.tasks.connector_indexers.google_gmail_indexer import index_google_gmail_messages
+ from app.tasks.connector_indexers.google_gmail_indexer import (
+ index_google_gmail_messages,
+ )
data = composio_gmail
mock_creds = MagicMock(name="composio-creds")
@@ -83,8 +96,10 @@ async def test_composio_gmail_uses_composio_credentials(
maker = make_session_factory(async_engine)
async with maker() as session:
await index_google_gmail_messages(
- session=session, connector_id=data["connector_id"],
- search_space_id=data["search_space_id"], user_id=data["user_id"],
+ session=session,
+ connector_id=data["connector_id"],
+ search_space_id=data["search_space_id"],
+ user_id=data["user_id"],
)
mock_build_creds.assert_called_once_with(_COMPOSIO_ACCOUNT_ID)
@@ -96,10 +111,15 @@ async def test_composio_gmail_uses_composio_credentials(
@patch(f"{_INDEXER_MODULE}.TaskLoggingService")
@patch(f"{_INDEXER_MODULE}.build_composio_credentials")
async def test_composio_gmail_without_account_id_returns_error(
- mock_build_creds, mock_tl_cls, async_engine, composio_gmail_no_id,
+ mock_build_creds,
+ mock_tl_cls,
+ async_engine,
+ composio_gmail_no_id,
):
"""Gmail indexer returns error when Composio connector lacks connected_account_id."""
- from app.tasks.connector_indexers.google_gmail_indexer import index_google_gmail_messages
+ from app.tasks.connector_indexers.google_gmail_indexer import (
+ index_google_gmail_messages,
+ )
data = composio_gmail_no_id
mock_tl_cls.return_value = mock_task_logger()
@@ -107,8 +127,10 @@ async def test_composio_gmail_without_account_id_returns_error(
maker = make_session_factory(async_engine)
async with maker() as session:
count, _skipped, error = await index_google_gmail_messages(
- session=session, connector_id=data["connector_id"],
- search_space_id=data["search_space_id"], user_id=data["user_id"],
+ session=session,
+ connector_id=data["connector_id"],
+ search_space_id=data["search_space_id"],
+ user_id=data["user_id"],
)
assert count == 0
@@ -121,10 +143,16 @@ async def test_composio_gmail_without_account_id_returns_error(
@patch(f"{_INDEXER_MODULE}.GoogleGmailConnector")
@patch(f"{_INDEXER_MODULE}.build_composio_credentials")
async def test_native_gmail_does_not_use_composio_credentials(
- mock_build_creds, mock_gmail_cls, mock_tl_cls, async_engine, native_gmail,
+ mock_build_creds,
+ mock_gmail_cls,
+ mock_tl_cls,
+ async_engine,
+ native_gmail,
):
"""Gmail indexer does NOT call build_composio_credentials for a native connector."""
- from app.tasks.connector_indexers.google_gmail_indexer import index_google_gmail_messages
+ from app.tasks.connector_indexers.google_gmail_indexer import (
+ index_google_gmail_messages,
+ )
data = native_gmail
mock_tl_cls.return_value = mock_task_logger()
@@ -136,8 +164,10 @@ async def test_native_gmail_does_not_use_composio_credentials(
maker = make_session_factory(async_engine)
async with maker() as session:
await index_google_gmail_messages(
- session=session, connector_id=data["connector_id"],
- search_space_id=data["search_space_id"], user_id=data["user_id"],
+ session=session,
+ connector_id=data["connector_id"],
+ search_space_id=data["search_space_id"],
+ user_id=data["user_id"],
)
mock_build_creds.assert_not_called()
diff --git a/surfsense_backend/tests/integration/google_unification/test_hybrid_search_type_filtering.py b/surfsense_backend/tests/integration/google_unification/test_hybrid_search_type_filtering.py
index bd84d5bb3..5a29b8333 100644
--- a/surfsense_backend/tests/integration/google_unification/test_hybrid_search_type_filtering.py
+++ b/surfsense_backend/tests/integration/google_unification/test_hybrid_search_type_filtering.py
@@ -39,9 +39,7 @@ async def test_list_of_types_returns_both_matching_doc_types(
assert "FILE" not in returned_types
-async def test_single_string_type_returns_only_that_type(
- db_session, seed_google_docs
-):
+async def test_single_string_type_returns_only_that_type(db_session, seed_google_docs):
"""Searching with a single string type returns only documents of that exact type."""
space_id = seed_google_docs["search_space"].id
diff --git a/surfsense_backend/tests/unit/google_unification/test_connector_credential_acceptance.py b/surfsense_backend/tests/unit/google_unification/test_connector_credential_acceptance.py
index 689aea40f..bb7bce8ed 100644
--- a/surfsense_backend/tests/unit/google_unification/test_connector_credential_acceptance.py
+++ b/surfsense_backend/tests/unit/google_unification/test_connector_credential_acceptance.py
@@ -64,7 +64,9 @@ async def test_gmail_accepts_valid_composio_credentials(mock_build):
mock_build.return_value = mock_service
connector = GoogleGmailConnector(
- creds, session=MagicMock(), user_id="test-user",
+ creds,
+ session=MagicMock(),
+ user_id="test-user",
)
profile, error = await connector.get_user_profile()
@@ -76,7 +78,9 @@ async def test_gmail_accepts_valid_composio_credentials(mock_build):
@patch("app.connectors.google_gmail_connector.Request")
@patch("app.connectors.google_gmail_connector.build")
-async def test_gmail_refreshes_expired_composio_credentials(mock_build, mock_request_cls):
+async def test_gmail_refreshes_expired_composio_credentials(
+ mock_build, mock_request_cls
+):
"""GoogleGmailConnector handles expired Composio credentials via refresh_handler
without attempting DB persistence."""
from app.connectors.google_gmail_connector import GoogleGmailConnector
@@ -95,7 +99,9 @@ async def test_gmail_refreshes_expired_composio_credentials(mock_build, mock_req
mock_session = AsyncMock()
connector = GoogleGmailConnector(
- creds, session=mock_session, user_id="test-user",
+ creds,
+ session=mock_session,
+ user_id="test-user",
)
profile, error = await connector.get_user_profile()
@@ -128,7 +134,9 @@ async def test_calendar_accepts_valid_composio_credentials(mock_build):
mock_build.return_value = mock_service
connector = GoogleCalendarConnector(
- creds, session=MagicMock(), user_id="test-user",
+ creds,
+ session=MagicMock(),
+ user_id="test-user",
)
calendars, error = await connector.get_calendars()
@@ -141,7 +149,9 @@ async def test_calendar_accepts_valid_composio_credentials(mock_build):
@patch("app.connectors.google_calendar_connector.Request")
@patch("app.connectors.google_calendar_connector.build")
-async def test_calendar_refreshes_expired_composio_credentials(mock_build, mock_request_cls):
+async def test_calendar_refreshes_expired_composio_credentials(
+ mock_build, mock_request_cls
+):
"""GoogleCalendarConnector handles expired Composio credentials via refresh_handler
without attempting DB persistence."""
from app.connectors.google_calendar_connector import GoogleCalendarConnector
@@ -157,7 +167,9 @@ async def test_calendar_refreshes_expired_composio_credentials(mock_build, mock_
mock_session = AsyncMock()
connector = GoogleCalendarConnector(
- creds, session=mock_session, user_id="test-user",
+ creds,
+ session=mock_session,
+ user_id="test-user",
)
calendars, error = await connector.get_calendars()
@@ -191,7 +203,9 @@ async def test_drive_client_uses_prebuilt_composio_credentials(mock_build):
mock_build.return_value = mock_service
client = GoogleDriveClient(
- session=MagicMock(), connector_id=999, credentials=creds,
+ session=MagicMock(),
+ connector_id=999,
+ credentials=creds,
)
files, next_token, error = await client.list_files()
@@ -218,7 +232,9 @@ async def test_drive_client_prebuilt_creds_skip_db_loading(mock_build, mock_get_
mock_build.return_value = mock_service
client = GoogleDriveClient(
- session=MagicMock(), connector_id=999, credentials=creds,
+ session=MagicMock(),
+ connector_id=999,
+ credentials=creds,
)
await client.list_files()
diff --git a/surfsense_backend/tests/unit/google_unification/test_schedule_checker_routing.py b/surfsense_backend/tests/unit/google_unification/test_schedule_checker_routing.py
index 38ef4d553..d2ced30b6 100644
--- a/surfsense_backend/tests/unit/google_unification/test_schedule_checker_routing.py
+++ b/surfsense_backend/tests/unit/google_unification/test_schedule_checker_routing.py
@@ -20,8 +20,14 @@ def test_drive_indexer_accepts_both_native_and_composio():
ACCEPTED_DRIVE_CONNECTOR_TYPES,
)
- assert SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR in ACCEPTED_DRIVE_CONNECTOR_TYPES
- assert SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR in ACCEPTED_DRIVE_CONNECTOR_TYPES
+ assert (
+ SearchSourceConnectorType.GOOGLE_DRIVE_CONNECTOR
+ in ACCEPTED_DRIVE_CONNECTOR_TYPES
+ )
+ assert (
+ SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR
+ in ACCEPTED_DRIVE_CONNECTOR_TYPES
+ )
def test_gmail_indexer_accepts_both_native_and_composio():
@@ -30,8 +36,14 @@ def test_gmail_indexer_accepts_both_native_and_composio():
ACCEPTED_GMAIL_CONNECTOR_TYPES,
)
- assert SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR in ACCEPTED_GMAIL_CONNECTOR_TYPES
- assert SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR in ACCEPTED_GMAIL_CONNECTOR_TYPES
+ assert (
+ SearchSourceConnectorType.GOOGLE_GMAIL_CONNECTOR
+ in ACCEPTED_GMAIL_CONNECTOR_TYPES
+ )
+ assert (
+ SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
+ in ACCEPTED_GMAIL_CONNECTOR_TYPES
+ )
def test_calendar_indexer_accepts_both_native_and_composio():
@@ -40,14 +52,29 @@ def test_calendar_indexer_accepts_both_native_and_composio():
ACCEPTED_CALENDAR_CONNECTOR_TYPES,
)
- assert SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR in ACCEPTED_CALENDAR_CONNECTOR_TYPES
- assert SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR in ACCEPTED_CALENDAR_CONNECTOR_TYPES
+ assert (
+ SearchSourceConnectorType.GOOGLE_CALENDAR_CONNECTOR
+ in ACCEPTED_CALENDAR_CONNECTOR_TYPES
+ )
+ assert (
+ SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
+ in ACCEPTED_CALENDAR_CONNECTOR_TYPES
+ )
def test_composio_connector_types_set_covers_all_google_services():
"""COMPOSIO_GOOGLE_CONNECTOR_TYPES should contain all three Composio Google types."""
from app.utils.google_credentials import COMPOSIO_GOOGLE_CONNECTOR_TYPES
- assert SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR in COMPOSIO_GOOGLE_CONNECTOR_TYPES
- assert SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR in COMPOSIO_GOOGLE_CONNECTOR_TYPES
- assert SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR in COMPOSIO_GOOGLE_CONNECTOR_TYPES
+ assert (
+ SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR
+ in COMPOSIO_GOOGLE_CONNECTOR_TYPES
+ )
+ assert (
+ SearchSourceConnectorType.COMPOSIO_GMAIL_CONNECTOR
+ in COMPOSIO_GOOGLE_CONNECTOR_TYPES
+ )
+ assert (
+ SearchSourceConnectorType.COMPOSIO_GOOGLE_CALENDAR_CONNECTOR
+ in COMPOSIO_GOOGLE_CONNECTOR_TYPES
+ )
diff --git a/surfsense_web/app/(home)/login/LocalLoginForm.tsx b/surfsense_web/app/(home)/login/LocalLoginForm.tsx
index b7a056d7f..5b5147926 100644
--- a/surfsense_web/app/(home)/login/LocalLoginForm.tsx
+++ b/surfsense_web/app/(home)/login/LocalLoginForm.tsx
@@ -213,11 +213,7 @@ export function LocalLoginForm() {
disabled={isLoggingIn}
className="w-full rounded-md bg-blue-600 px-4 py-1.5 md:py-2 text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 transition-all text-sm md:text-base flex items-center justify-center gap-2"
>
- {isLoggingIn ? (
-
+ Your Google Drive authentication has expired. Please re-authenticate using the button + below. +
+ )} - {isEditMode ? ( -{pickerError}
- )} + {pickerError && !isAuthExpired &&{pickerError}
} - {isAuthExpired && ( -- Your Google Drive authentication has expired. Please re-authenticate using the button below. -
- )} + {isAuthExpired && ( ++ Your Google Drive authentication has expired. Please re-authenticate using the button + below. +
+ )} {/* Indexing Options */} diff --git a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx index e950cb648..93d280a15 100644 --- a/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx +++ b/surfsense_web/components/assistant-ui/connector-popup/connector-configs/views/connector-edit-view.tsx @@ -220,10 +220,8 @@ export const ConnectorEditView: FC