feat: add list_recent_activity tool (Drive Activity API)#535
Conversation
Adds a new Drive tool that lists recently modified, viewed, shared, or created files across ALL of Google Drive (not limited to a single folder). Features: - Sort by modifiedTime, viewedByMeTime, sharedWithMeTime, or createdTime - Filter by ownership: all, owned (my files), or shared (others' files) - Filter by file type (doc, sheet, pdf, etc.) - Returns lastModifyingUser and owner info for each file - Pagination support via page_token Also adds backward-compatible `order_by` parameter to build_drive_list_params helper. Uses existing Drive API v3 files.list — no new scopes or auth required. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lastModifyingUser - list_recent_files: now includes lastModifyingUser and owner info - list_recent_activity: new tool using Drive Activity API v2 for collaboration feed (who did what, when, to which file) - Added driveactivity service config and drive.activity.readonly scope - Added orderBy support to build_drive_list_params helper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughIntroduces Google Drive Activity API support by adding a new read-only scope constant, extending authentication configurations, enabling Drive service integration, adding optional ordering to list operations, and implementing two new async tools for querying recent file modifications and activity logs with comprehensive filtering and pagination. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
gdrive/drive_tools.py (1)
782-793: Minor: Default label "folder" may be misleading.When parent folders lack a
title, the default"folder"is used regardless of the actual item type. This could produce output like "moved to folder from folder" which isn't very informative.Consider using a more generic default or extracting additional context:
💡 Suggested improvement
elif "move" in action_detail: move = action_detail["move"] added = move.get("addedParents", []) removed = move.get("removedParents", []) parts = [] if added: - titles = [p.get("driveItem", {}).get("title", "folder") for p in added] + titles = [p.get("driveItem", {}).get("title") or "(unknown)" for p in added] parts.append(f"moved to {', '.join(titles)}") if removed: - titles = [p.get("driveItem", {}).get("title", "folder") for p in removed] + titles = [p.get("driveItem", {}).get("title") or "(unknown)" for p in removed] parts.append(f"from {', '.join(titles)}") return " ".join(parts) if parts else "moved"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@gdrive/drive_tools.py` around lines 782 - 793, The fallback label "folder" in the move-handling block (variables: action_detail -> move, added, removed, addedParents, removedParents, parts, titles) can be misleading; update the two list comprehensions that build titles to use a more generic default like "item" or derive a better label from the driveItem (e.g., driveItem.get("mimeType") or driveItem.get("type")) when title is missing, so replace p.get("driveItem", {}).get("title", "folder") with something like p.get("driveItem", {}).get("title") or p.get("driveItem", {}).get("mimeType") or "item" to produce clearer messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@gdrive/drive_tools.py`:
- Around line 714-732: The code is appending People API resource names
(knownUser.personName like "people/123...") as if they were display names;
update the actor-processing logic (the loop using actors, actor_names,
knownUser, person_name) to detect when person_name starts with "people/" and
either (A) resolve it to a human-readable name via the People API (call
people_service.people().get(resourceName=person_name, personFields='names') and
use the first names[0].displayName, with proper error handling and honoring the
contacts_read scope) or (B) if you cannot call the People API, change the
fallback to a clearer placeholder and document the limitation in the function's
docstring/return description; ensure all fallbacks still add entries to
actor_names and that the rest of the checks (deletedUser, administrator, system,
impersonation, unknown) remain intact.
---
Nitpick comments:
In `@gdrive/drive_tools.py`:
- Around line 782-793: The fallback label "folder" in the move-handling block
(variables: action_detail -> move, added, removed, addedParents, removedParents,
parts, titles) can be misleading; update the two list comprehensions that build
titles to use a more generic default like "item" or derive a better label from
the driveItem (e.g., driveItem.get("mimeType") or driveItem.get("type")) when
title is missing, so replace p.get("driveItem", {}).get("title", "folder") with
something like p.get("driveItem", {}).get("title") or p.get("driveItem",
{}).get("mimeType") or "item" to produce clearer messages.
| actors = activity.get("actors", []) | ||
| actor_names = [] | ||
| for actor in actors: | ||
| user_info = actor.get("user", {}) | ||
| known_user = user_info.get("knownUser", {}) | ||
| person_name = known_user.get("personName", "") | ||
| if person_name: | ||
| actor_names.append(person_name) | ||
| elif user_info.get("deletedUser"): | ||
| actor_names.append("(deleted user)") | ||
| elif actor.get("administrator"): | ||
| actor_names.append("(admin)") | ||
| elif actor.get("system"): | ||
| actor_names.append("(system)") | ||
| elif actor.get("impersonation"): | ||
| actor_names.append("(impersonation)") | ||
| else: | ||
| actor_names.append("(unknown)") | ||
| actor_str = ", ".join(actor_names) if actor_names else "(unknown)" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Google Drive Activity API v2 KnownUser personName field format documentation
💡 Result:
In Google Drive Activity API v2, actors[].user.knownUser.personName is a People API resource name. The documented format is:
people/ACCOUNT_ID(a.k.a. the user’s People API “person” resource name) [1]- Google’s Drive Activity v2 data model examples also show
personName: "people/ACCOUNT_ID"[2]
You can pass this personName to the People API (for example, people.get) to fetch more details—subject to what the caller is permitted to access [1].
Sources: [1], [2]
Actor names will display as resource identifiers instead of human-readable names.
The personName field from knownUser is a People API resource name in the format people/ACCOUNT_ID, not a display name. The current code adds these resource identifiers directly to the output, resulting in actors being displayed as people/12345678901234567890 instead of readable names.
Consider either:
- Documenting this limitation in the function's return description
- Using the People API to resolve
personNameto display names (requirescontacts_readscope) - Checking if other user information fields are available in the activity data that contain readable names
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gdrive/drive_tools.py` around lines 714 - 732, The code is appending People
API resource names (knownUser.personName like "people/123...") as if they were
display names; update the actor-processing logic (the loop using actors,
actor_names, knownUser, person_name) to detect when person_name starts with
"people/" and either (A) resolve it to a human-readable name via the People API
(call people_service.people().get(resourceName=person_name,
personFields='names') and use the first names[0].displayName, with proper error
handling and honoring the contacts_read scope) or (B) if you cannot call the
People API, change the fallback to a clearer placeholder and document the
limitation in the function's docstring/return description; ensure all fallbacks
still add entries to actor_names and that the rest of the checks (deletedUser,
administrator, system, impersonation, unknown) remain intact.
|
Same comment as the other PR, would like to build this all into an existing tool. Thanks! |
Summary
list_recent_activitytool that shows WHO did WHAT and WHEN across Google Drivedriveactivity.googleapis.com) for a collaboration feeddrive.activity.readonlyscope anddriveactivityservice configMotivation
list_recent_files(PR #534) shows what files changed recently, but not who changed them or what they did.list_recent_activityfills this gap — it's the collaboration log. Useful for seeing teammate edits, shares, comments, renames, and moves in chronological order.Changes
auth/scopes.py: AddDRIVE_ACTIVITY_READONLY_SCOPEconstant and include inDRIVE_SCOPESauth/service_decorator.py: Adddriveactivityservice config anddrive_activity_readscope groupgdrive/drive_tools.py: Addlist_recent_activitytool and_format_activity_actionhelperNote
Requires the Drive Activity API to be enabled in the Google Cloud project. The tool gracefully returns the standard API enablement error if it's not enabled.
Test plan
list_recent_activitywith default params (all Drive activity)nextPageTokendrive.activity.readonlyis missing🤖 Generated with Claude Code
Summary by CodeRabbit