Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion backend/app/controller/tool_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,43 @@ async def uninstall_tool(tool: str):
oauth_state_manager._states.pop("google_calendar", None)
logger.info("Cleared Google Calendar OAuth state cache")

return {
"success": True,
"message": f"Successfully uninstalled {tool} and cleaned up authentication tokens"
}
except Exception as e:
logger.error(f"Failed to uninstall {tool}: {e}")
raise HTTPException(
status_code=500,
detail=f"Failed to uninstall {tool}: {str(e)}"
)
elif tool == "google_gmail":
try:
# Clean up Google Gmail token directories (user-scoped + legacy)
token_dirs = set()
try:
token_dirs.add(os.path.dirname(GoogleGmailNativeToolkit._build_canonical_token_path()))
except Exception as e:
logger.warning(f"Failed to resolve canonical Google Gmail token path: {e}")

token_dirs.add(os.path.join(os.path.expanduser("~"), ".eigent", "tokens", "google_gmail"))

for token_dir in token_dirs:
if os.path.exists(token_dir):
shutil.rmtree(token_dir)
logger.info(f"Removed Google Gmail token directory: {token_dir}")

# Clear OAuth state manager cache (this is the key fix!)
# This removes the cached credentials from memory
state = oauth_state_manager.get_state("google_gmail")
if state:
if state.status in ["pending", "authorizing"]:
state.cancel()
logger.info("Cancelled ongoing Google Gmail authorization")
# Clear the state completely to remove cached credentials
oauth_state_manager._states.pop("google_gmail", None)
logger.info("Cleared Google Gmail OAuth state cache")

return {
"success": True,
"message": f"Successfully uninstalled {tool} and cleaned up authentication tokens"
Expand All @@ -342,7 +379,7 @@ async def uninstall_tool(tool: str):
else:
raise HTTPException(
status_code=404,
detail=f"Tool '{tool}' not found. Available tools: ['notion', 'google_calendar']"
detail=f"Tool '{tool}' not found. Available tools: ['notion', 'google_calendar', 'google_gmail']"
)


Expand Down
38 changes: 25 additions & 13 deletions backend/app/utils/toolkit/google_gmail_native_toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

from camel.toolkits import GmailToolkit as BaseGmailToolkit
from camel.toolkits.function_tool import FunctionTool
from loguru import logger

from app.component.environment import env
from app.service.task import Agents
from app.utils.listen.toolkit_listen import listen_toolkit
from app.utils.toolkit.abstract_toolkit import AbstractToolkit
from app.utils.oauth_state_manager import oauth_state_manager
from utils import traceroot_wrapper as traceroot

logger = traceroot.get_logger("main")

SCOPES = [
'https://www.googleapis.com/auth/gmail.readonly',
Expand All @@ -19,7 +21,6 @@
'https://www.googleapis.com/auth/gmail.compose',
'https://www.googleapis.com/auth/gmail.labels',
'https://www.googleapis.com/auth/contacts.readonly',
'https://www.googleapis.com/auth/people.readonly'
]


Expand All @@ -41,16 +42,26 @@ def __init__(
"""
self.api_task_id = api_task_id
self._token_path = (
os.environ.get("GOOGLE_GMAIL_TOKEN_PATH")
env("GOOGLE_GMAIL_TOKEN_PATH")
or os.path.join(
os.path.expanduser("~"),
".eigent",
"tokens",
"google_gmail",
f"google_gmail_token_{api_task_id}.json",
"google_gmail_token.json",
)
)
super().__init__(timeout=timeout)

@classmethod
def _build_canonical_token_path(cls) -> str:
return env("GOOGLE_GMAIL_TOKEN_PATH") or os.path.join(
os.path.expanduser("~"),
".eigent",
"tokens",
"google_gmail",
"google_gmail_token.json",
)

# Email Sending Operations
@listen_toolkit(
Expand Down Expand Up @@ -159,8 +170,9 @@ def list_threads(
max_results: int = 10,
include_spam_trash: bool = False,
label_ids: Optional[List[str]] = None,
page_token: Optional[str] = None,
) -> Dict[str, Any]:
return super().list_threads(query, max_results, include_spam_trash, label_ids)
return super().list_threads(query, max_results, include_spam_trash, label_ids, page_token)

# Label Management
@listen_toolkit(
Expand Down Expand Up @@ -303,10 +315,10 @@ def _authenticate(self):

# If no token file, try environment variables
if not creds:
client_id = os.environ.get("GOOGLE_CLIENT_ID")
client_secret = os.environ.get("GOOGLE_CLIENT_SECRET")
refresh_token = os.environ.get("GOOGLE_REFRESH_TOKEN")
token_uri = os.environ.get("GOOGLE_TOKEN_URI", "https://oauth2.googleapis.com/token")
client_id = env("GOOGLE_CLIENT_ID")
client_secret = env("GOOGLE_CLIENT_SECRET")
refresh_token = env("GOOGLE_REFRESH_TOKEN")
token_uri = env("GOOGLE_TOKEN_URI", "https://oauth2.googleapis.com/token")

if refresh_token and client_id and client_secret:
logger.info("Creating credentials from environment variables")
Expand Down Expand Up @@ -378,9 +390,9 @@ def auth_flow():
state.status = "authorizing"
oauth_state_manager.update_status("google_gmail", "authorizing")

client_id = os.environ.get("GOOGLE_CLIENT_ID")
client_secret = os.environ.get("GOOGLE_CLIENT_SECRET")
token_uri = os.environ.get("GOOGLE_TOKEN_URI", "https://oauth2.googleapis.com/token")
client_id = env("GOOGLE_CLIENT_ID")
client_secret = env("GOOGLE_CLIENT_SECRET")
token_uri = env("GOOGLE_TOKEN_URI", "https://oauth2.googleapis.com/token")

logger.info(f"Google Gmail auth - client_id present: {bool(client_id)}, client_secret present: {bool(client_secret)}")

Expand Down Expand Up @@ -437,7 +449,7 @@ def auth_flow():
".eigent",
"tokens",
"google_gmail",
f"google_gmail_token_{api_task_id}.json",
f"google_gmail_token.json",
)

try:
Expand Down
Loading