diff --git a/apps/docs/content/docs/en/tools/google_drive.mdx b/apps/docs/content/docs/en/tools/google_drive.mdx index 27de721e6e..54b3ed3bc3 100644 --- a/apps/docs/content/docs/en/tools/google_drive.mdx +++ b/apps/docs/content/docs/en/tools/google_drive.mdx @@ -48,7 +48,7 @@ Integrate Google Drive into the workflow. Can create, upload, and list files. ### `google_drive_upload` -Upload a file to Google Drive +Upload a file to Google Drive with complete metadata returned #### Input @@ -65,11 +65,11 @@ Upload a file to Google Drive | Parameter | Type | Description | | --------- | ---- | ----------- | -| `file` | json | Uploaded file metadata including ID, name, and links | +| `file` | object | Complete uploaded file metadata from Google Drive | ### `google_drive_create_folder` -Create a new folder in Google Drive +Create a new folder in Google Drive with complete metadata returned #### Input @@ -83,11 +83,11 @@ Create a new folder in Google Drive | Parameter | Type | Description | | --------- | ---- | ----------- | -| `file` | json | Created folder metadata including ID, name, and parent information | +| `file` | object | Complete created folder metadata from Google Drive | ### `google_drive_download` -Download a file from Google Drive (exports Google Workspace files automatically) +Download a file from Google Drive with complete metadata (exports Google Workspace files automatically) #### Input @@ -96,16 +96,17 @@ Download a file from Google Drive (exports Google Workspace files automatically) | `fileId` | string | Yes | The ID of the file to download | | `mimeType` | string | No | The MIME type to export Google Workspace files to \(optional\) | | `fileName` | string | No | Optional filename override | +| `includeRevisions` | boolean | No | Whether to include revision history in the metadata \(default: true\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | -| `file` | file | Downloaded file stored in execution files | +| `file` | object | Downloaded file stored in execution files | ### `google_drive_list` -List files and folders in Google Drive +List files and folders in Google Drive with complete metadata #### Input @@ -121,7 +122,7 @@ List files and folders in Google Drive | Parameter | Type | Description | | --------- | ---- | ----------- | -| `files` | json | Array of file metadata objects from the specified folder | +| `files` | array | Array of file metadata objects from Google Drive | diff --git a/apps/sim/tools/google_drive/create_folder.ts b/apps/sim/tools/google_drive/create_folder.ts index 6fb03f4b4f..6e04de4804 100644 --- a/apps/sim/tools/google_drive/create_folder.ts +++ b/apps/sim/tools/google_drive/create_folder.ts @@ -1,10 +1,14 @@ +import { createLogger } from '@sim/logger' import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from '@/tools/google_drive/types' +import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils' import type { ToolConfig } from '@/tools/types' +const logger = createLogger('GoogleDriveCreateFolderTool') + export const createFolderTool: ToolConfig = { id: 'google_drive_create_folder', name: 'Create Folder in Google Drive', - description: 'Create a new folder in Google Drive', + description: 'Create a new folder in Google Drive with complete metadata returned', version: '1.0', oauth: { @@ -66,35 +70,120 @@ export const createFolderTool: ToolConfig { + transformResponse: async (response: Response, params?: GoogleDriveToolParams) => { if (!response.ok) { const data = await response.json().catch(() => ({})) + logger.error('Failed to create folder in Google Drive', { + status: response.status, + statusText: response.statusText, + error: data, + }) throw new Error(data.error?.message || 'Failed to create folder in Google Drive') } + const data = await response.json() + const folderId = data.id + const authHeader = `Bearer ${params?.accessToken || ''}` + + // Fetch complete folder metadata with all fields + const metadataResponse = await fetch( + `https://www.googleapis.com/drive/v3/files/${folderId}?supportsAllDrives=true&fields=${ALL_FILE_FIELDS}`, + { + headers: { + Authorization: authHeader, + }, + } + ) + + if (!metadataResponse.ok) { + logger.warn('Failed to fetch complete metadata, returning basic response', { + status: metadataResponse.status, + statusText: metadataResponse.statusText, + }) + // Return basic response if metadata fetch fails + return { + success: true, + output: { + file: data, + }, + } + } + + const fullMetadata = await metadataResponse.json() + + logger.info('Folder created successfully', { + folderId: fullMetadata.id, + name: fullMetadata.name, + mimeType: fullMetadata.mimeType, + hasOwners: !!fullMetadata.owners?.length, + hasPermissions: !!fullMetadata.permissions?.length, + }) return { success: true, output: { - file: { - id: data.id, - name: data.name, - mimeType: data.mimeType, - webViewLink: data.webViewLink, - webContentLink: data.webContentLink, - size: data.size, - createdTime: data.createdTime, - modifiedTime: data.modifiedTime, - parents: data.parents, - }, + file: fullMetadata, }, } }, outputs: { file: { - type: 'json', - description: 'Created folder metadata including ID, name, and parent information', + type: 'object', + description: 'Complete created folder metadata from Google Drive', + properties: { + // Basic Info + id: { type: 'string', description: 'Google Drive folder ID' }, + name: { type: 'string', description: 'Folder name' }, + mimeType: { type: 'string', description: 'MIME type (application/vnd.google-apps.folder)' }, + kind: { type: 'string', description: 'Resource type identifier' }, + description: { type: 'string', description: 'Folder description' }, + // Ownership & Sharing + owners: { type: 'json', description: 'List of folder owners' }, + permissions: { type: 'json', description: 'Folder permissions' }, + permissionIds: { type: 'json', description: 'Permission IDs' }, + shared: { type: 'boolean', description: 'Whether folder is shared' }, + ownedByMe: { type: 'boolean', description: 'Whether owned by current user' }, + writersCanShare: { type: 'boolean', description: 'Whether writers can share' }, + viewersCanCopyContent: { type: 'boolean', description: 'Whether viewers can copy' }, + copyRequiresWriterPermission: { + type: 'boolean', + description: 'Whether copy requires writer permission', + }, + sharingUser: { type: 'json', description: 'User who shared the folder' }, + // Labels/Tags + starred: { type: 'boolean', description: 'Whether folder is starred' }, + trashed: { type: 'boolean', description: 'Whether folder is in trash' }, + explicitlyTrashed: { type: 'boolean', description: 'Whether explicitly trashed' }, + properties: { type: 'json', description: 'Custom properties' }, + appProperties: { type: 'json', description: 'App-specific properties' }, + folderColorRgb: { type: 'string', description: 'Folder color' }, + // Timestamps + createdTime: { type: 'string', description: 'Folder creation time' }, + modifiedTime: { type: 'string', description: 'Last modification time' }, + modifiedByMeTime: { type: 'string', description: 'When modified by current user' }, + viewedByMeTime: { type: 'string', description: 'When last viewed by current user' }, + sharedWithMeTime: { type: 'string', description: 'When shared with current user' }, + // User Info + lastModifyingUser: { type: 'json', description: 'User who last modified the folder' }, + viewedByMe: { type: 'boolean', description: 'Whether viewed by current user' }, + modifiedByMe: { type: 'boolean', description: 'Whether modified by current user' }, + // Links + webViewLink: { type: 'string', description: 'URL to view in browser' }, + iconLink: { type: 'string', description: 'URL to folder icon' }, + // Hierarchy & Location + parents: { type: 'json', description: 'Parent folder IDs' }, + spaces: { type: 'json', description: 'Spaces containing folder' }, + driveId: { type: 'string', description: 'Shared drive ID' }, + // Capabilities + capabilities: { type: 'json', description: 'User capabilities on folder' }, + // Versions + version: { type: 'string', description: 'Version number' }, + // Other + isAppAuthorized: { type: 'boolean', description: 'Whether created by requesting app' }, + contentRestrictions: { type: 'json', description: 'Content restrictions' }, + linkShareMetadata: { type: 'json', description: 'Link share metadata' }, + }, }, }, } diff --git a/apps/sim/tools/google_drive/download.ts b/apps/sim/tools/google_drive/download.ts index c01d1a0475..db58ac3d88 100644 --- a/apps/sim/tools/google_drive/download.ts +++ b/apps/sim/tools/google_drive/download.ts @@ -1,6 +1,16 @@ import { createLogger } from '@sim/logger' -import type { GoogleDriveDownloadResponse, GoogleDriveToolParams } from '@/tools/google_drive/types' -import { DEFAULT_EXPORT_FORMATS, GOOGLE_WORKSPACE_MIME_TYPES } from '@/tools/google_drive/utils' +import type { + GoogleDriveDownloadResponse, + GoogleDriveFile, + GoogleDriveRevision, + GoogleDriveToolParams, +} from '@/tools/google_drive/types' +import { + ALL_FILE_FIELDS, + ALL_REVISION_FIELDS, + DEFAULT_EXPORT_FORMATS, + GOOGLE_WORKSPACE_MIME_TYPES, +} from '@/tools/google_drive/utils' import type { ToolConfig } from '@/tools/types' const logger = createLogger('GoogleDriveDownloadTool') @@ -8,7 +18,8 @@ const logger = createLogger('GoogleDriveDownloadTool') export const downloadTool: ToolConfig = { id: 'google_drive_download', name: 'Download File from Google Drive', - description: 'Download a file from Google Drive (exports Google Workspace files automatically)', + description: + 'Download a file from Google Drive with complete metadata (exports Google Workspace files automatically)', version: '1.0', oauth: { @@ -41,11 +52,18 @@ export const downloadTool: ToolConfig - `https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=id,name,mimeType&supportsAllDrives=true`, + `https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=${ALL_FILE_FIELDS}&supportsAllDrives=true`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, @@ -64,7 +82,7 @@ export const downloadTool: ToolConfig - `https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=id,name,mimeType&supportsAllDrives=true`, + `https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=${ALL_FILE_FIELDS}&supportsAllDrives=true`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.accessToken}`, @@ -61,7 +75,7 @@ export const getContentTool: ToolConfig = { id: 'google_drive_list', name: 'List Google Drive Files', - description: 'List files and folders in Google Drive', + description: 'List files and folders in Google Drive with complete metadata', version: '1.0', oauth: { @@ -55,20 +56,22 @@ export const listTool: ToolConfig { const url = new URL('https://www.googleapis.com/drive/v3/files') - url.searchParams.append( - 'fields', - 'files(id,name,mimeType,webViewLink,webContentLink,size,createdTime,modifiedTime,parents),nextPageToken' - ) + url.searchParams.append('fields', `files(${ALL_FILE_FIELDS}),nextPageToken`) // Ensure shared drives support - corpora=allDrives is critical for searching across shared drives url.searchParams.append('corpora', 'allDrives') url.searchParams.append('supportsAllDrives', 'true') url.searchParams.append('includeItemsFromAllDrives', 'true') + // Helper to escape single quotes for Google Drive query syntax + const escapeQueryValue = (value: string): string => + value.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + // Build the query conditions const conditions = ['trashed = false'] // Always exclude trashed files const folderId = params.folderId || params.folderSelector if (folderId) { - conditions.push(`'${folderId}' in parents`) + const escapedFolderId = escapeQueryValue(folderId) + conditions.push(`'${escapedFolderId}' in parents`) } // Combine all conditions with AND @@ -76,7 +79,8 @@ export const listTool: ToolConfig ({ - id: file.id, - name: file.name, - mimeType: file.mimeType, - webViewLink: file.webViewLink, - webContentLink: file.webContentLink, - size: file.size, - createdTime: file.createdTime, - modifiedTime: file.modifiedTime, - parents: file.parents, - })), + files: data.files, nextPageToken: data.nextPageToken, }, } @@ -122,8 +116,86 @@ export const listTool: ToolConfig +} + +// Label/tag information +export interface GoogleDriveLabel { + id?: string + revisionId?: string + kind?: string + fields?: Record< + string, + { + kind?: string + id?: string + valueType?: string + dateString?: string[] + integer?: string[] + selection?: string[] + text?: string[] + user?: GoogleDriveUser[] + } + > +} + +// Content hints for indexing +export interface GoogleDriveContentHints { + indexableText?: string + thumbnail?: { + image?: string + mimeType?: string + } +} + +// Image-specific metadata +export interface GoogleDriveImageMediaMetadata { + width?: number + height?: number + rotation?: number + time?: string + cameraMake?: string + cameraModel?: string + exposureTime?: number + aperture?: number + flashUsed?: boolean + focalLength?: number + isoSpeed?: number + meteringMode?: string + sensor?: string + exposureMode?: string + colorSpace?: string + whiteBalance?: string + exposureBias?: number + maxApertureValue?: number + subjectDistance?: number + lens?: string + location?: { + latitude?: number + longitude?: number + altitude?: number + } +} + +// Video-specific metadata +export interface GoogleDriveVideoMediaMetadata { + width?: number + height?: number + durationMillis?: string +} + +// Shortcut details +export interface GoogleDriveShortcutDetails { + targetId?: string + targetMimeType?: string + targetResourceKey?: string +} + +// Content restrictions +export interface GoogleDriveContentRestriction { + readOnly?: boolean + reason?: string + type?: string + restrictingUser?: GoogleDriveUser + restrictionTime?: string + ownerRestricted?: boolean + systemRestricted?: boolean +} + +// Link share metadata +export interface GoogleDriveLinkShareMetadata { + securityUpdateEligible?: boolean + securityUpdateEnabled?: boolean +} + +// Capabilities - what the current user can do with the file +export interface GoogleDriveCapabilities { + canAcceptOwnership?: boolean + canAddChildren?: boolean + canAddFolderFromAnotherDrive?: boolean + canAddMyDriveParent?: boolean + canChangeCopyRequiresWriterPermission?: boolean + canChangeSecurityUpdateEnabled?: boolean + canChangeViewersCanCopyContent?: boolean + canComment?: boolean + canCopy?: boolean + canDelete?: boolean + canDeleteChildren?: boolean + canDownload?: boolean + canEdit?: boolean + canListChildren?: boolean + canModifyContent?: boolean + canModifyContentRestriction?: boolean + canModifyEditorContentRestriction?: boolean + canModifyLabels?: boolean + canModifyOwnerContentRestriction?: boolean + canMoveChildrenOutOfDrive?: boolean + canMoveChildrenOutOfTeamDrive?: boolean + canMoveChildrenWithinDrive?: boolean + canMoveChildrenWithinTeamDrive?: boolean + canMoveItemIntoTeamDrive?: boolean + canMoveItemOutOfDrive?: boolean + canMoveItemOutOfTeamDrive?: boolean + canMoveItemWithinDrive?: boolean + canMoveItemWithinTeamDrive?: boolean + canMoveTeamDriveItem?: boolean + canReadDrive?: boolean + canReadLabels?: boolean + canReadRevisions?: boolean + canReadTeamDrive?: boolean + canRemoveChildren?: boolean + canRemoveContentRestriction?: boolean + canRemoveMyDriveParent?: boolean + canRename?: boolean + canShare?: boolean + canTrash?: boolean + canTrashChildren?: boolean + canUntrash?: boolean +} + +// Revision information +export interface GoogleDriveRevision { + id?: string + mimeType?: string + modifiedTime?: string + keepForever?: boolean + published?: boolean + publishAuto?: boolean + publishedLink?: string + publishedOutsideDomain?: boolean + lastModifyingUser?: GoogleDriveUser + originalFilename?: string + md5Checksum?: string + size?: string + exportLinks?: Record + kind?: string +} + +// Complete file metadata - all 50+ fields from Google Drive API v3 export interface GoogleDriveFile { + // Basic Info id: string name: string mimeType: string + kind?: string + description?: string + originalFilename?: string + fullFileExtension?: string + fileExtension?: string + + // Ownership & Sharing + owners?: GoogleDriveUser[] + permissions?: GoogleDrivePermission[] + permissionIds?: string[] + shared?: boolean + ownedByMe?: boolean + writersCanShare?: boolean + viewersCanCopyContent?: boolean + copyRequiresWriterPermission?: boolean + sharingUser?: GoogleDriveUser + + // Labels/Tags + labels?: GoogleDriveLabel[] + labelInfo?: { + labels?: GoogleDriveLabel[] + } + starred?: boolean + trashed?: boolean + explicitlyTrashed?: boolean + properties?: Record + appProperties?: Record + folderColorRgb?: string + + // Timestamps + createdTime?: string + modifiedTime?: string + modifiedByMeTime?: string + viewedByMeTime?: string + sharedWithMeTime?: string + trashedTime?: string + + // User Info + lastModifyingUser?: GoogleDriveUser + trashingUser?: GoogleDriveUser + viewedByMe?: boolean + modifiedByMe?: boolean + + // Links webViewLink?: string webContentLink?: string + iconLink?: string + thumbnailLink?: string + exportLinks?: Record + + // Size & Storage size?: string - createdTime?: string - modifiedTime?: string + quotaBytesUsed?: string + + // Checksums + md5Checksum?: string + sha1Checksum?: string + sha256Checksum?: string + + // Hierarchy & Location parents?: string[] + spaces?: string[] + driveId?: string + teamDriveId?: string + + // Capabilities + capabilities?: GoogleDriveCapabilities + + // Versions + version?: string + headRevisionId?: string + + // Media Metadata + hasThumbnail?: boolean + thumbnailVersion?: string + imageMediaMetadata?: GoogleDriveImageMediaMetadata + videoMediaMetadata?: GoogleDriveVideoMediaMetadata + contentHints?: GoogleDriveContentHints + + // Other + isAppAuthorized?: boolean + contentRestrictions?: GoogleDriveContentRestriction[] + resourceKey?: string + shortcutDetails?: GoogleDriveShortcutDetails + linkShareMetadata?: GoogleDriveLinkShareMetadata + + // Revisions (fetched separately but included in response) + revisions?: GoogleDriveRevision[] } export interface GoogleDriveListResponse extends ToolResponse { @@ -37,9 +304,10 @@ export interface GoogleDriveDownloadResponse extends ToolResponse { file: { name: string mimeType: string - data: Buffer + data: string size: number } + metadata: GoogleDriveFile } } @@ -56,6 +324,7 @@ export interface GoogleDriveToolParams { pageSize?: number pageToken?: string exportMimeType?: string + includeRevisions?: boolean } export type GoogleDriveResponse = diff --git a/apps/sim/tools/google_drive/upload.ts b/apps/sim/tools/google_drive/upload.ts index 5d8f0c6747..df321fe9f0 100644 --- a/apps/sim/tools/google_drive/upload.ts +++ b/apps/sim/tools/google_drive/upload.ts @@ -1,6 +1,7 @@ import { createLogger } from '@sim/logger' import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from '@/tools/google_drive/types' import { + ALL_FILE_FIELDS, GOOGLE_WORKSPACE_MIME_TYPES, handleSheetsFormat, SOURCE_MIME_TYPES, @@ -12,7 +13,7 @@ const logger = createLogger('GoogleDriveUploadTool') export const uploadTool: ToolConfig = { id: 'google_drive_upload', name: 'Upload to Google Drive', - description: 'Upload a file to Google Drive', + description: 'Upload a file to Google Drive with complete metadata returned', version: '1.0', oauth: { @@ -228,8 +229,9 @@ export const uploadTool: ToolConfig