improvement(google-drive) (#2752)

* expanded metadata fields for google drive

* added tag dropdown support

* fixed greptile

* added utils func

* removed comments

* updated docs

* greptile comments

* fixed output schema

* reverted back to bas64 string
This commit is contained in:
Adam Gough
2026-01-09 16:56:07 -08:00
committed by GitHub
parent d3d6012d5c
commit 796f73ee01
8 changed files with 950 additions and 100 deletions

View File

@@ -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 |

View File

@@ -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<GoogleDriveToolParams, GoogleDriveUploadResponse> = {
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<GoogleDriveToolParams, GoogleDriveUplo
},
},
transformResponse: async (response: Response) => {
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' },
},
},
},
}

View File

@@ -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<GoogleDriveToolParams, GoogleDriveDownloadResponse> = {
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<GoogleDriveToolParams, GoogleDriveDownload
visibility: 'user-only',
description: 'Optional filename override',
},
includeRevisions: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
'Whether to include revision history in the metadata (default: true, returns first 100 revisions)',
},
},
request: {
url: (params) =>
`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<GoogleDriveToolParams, GoogleDriveDownload
throw new Error(errorDetails.error?.message || 'Failed to get file metadata')
}
const metadata = await response.json()
const metadata: GoogleDriveFile = await response.json()
const fileId = metadata.id
const mimeType = metadata.mimeType
const authHeader = `Bearer ${params?.accessToken || ''}`
@@ -132,6 +150,43 @@ export const downloadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveDownload
fileBuffer = Buffer.from(arrayBuffer)
}
const includeRevisions = params?.includeRevisions !== false
const canReadRevisions = metadata.capabilities?.canReadRevisions === true
if (includeRevisions && canReadRevisions) {
try {
const revisionsResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}/revisions?fields=revisions(${ALL_REVISION_FIELDS})&pageSize=100`,
{
headers: {
Authorization: authHeader,
},
}
)
if (revisionsResponse.ok) {
const revisionsData = await revisionsResponse.json()
metadata.revisions = revisionsData.revisions as GoogleDriveRevision[]
logger.info('Fetched file revisions', {
fileId,
revisionCount: metadata.revisions?.length || 0,
})
} else {
logger.warn('Failed to fetch revisions, continuing without them', {
status: revisionsResponse.status,
statusText: revisionsResponse.statusText,
})
}
} catch (revisionError: any) {
logger.warn('Error fetching revisions, continuing without them', {
error: revisionError.message,
})
}
} else if (includeRevisions && !canReadRevisions) {
logger.info('Skipping revision fetch - user does not have canReadRevisions permission', {
fileId,
})
}
const resolvedName = params?.fileName || metadata.name || 'download'
logger.info('File downloaded successfully', {
@@ -139,17 +194,23 @@ export const downloadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveDownload
name: resolvedName,
size: fileBuffer.length,
mimeType: finalMimeType,
hasOwners: !!metadata.owners?.length,
hasPermissions: !!metadata.permissions?.length,
hasRevisions: !!metadata.revisions?.length,
})
const base64Data = fileBuffer.toString('base64')
return {
success: true,
output: {
file: {
name: resolvedName,
mimeType: finalMimeType,
data: fileBuffer,
data: base64Data,
size: fileBuffer.length,
},
metadata,
},
}
} catch (error: any) {
@@ -162,6 +223,95 @@ export const downloadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveDownload
},
outputs: {
file: { type: 'file', description: 'Downloaded file stored in execution files' },
file: {
type: 'object',
description: 'Downloaded file data',
properties: {
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type of the file' },
data: { type: 'string', description: 'File content as base64-encoded string' },
size: { type: 'number', description: 'File size in bytes' },
},
},
metadata: {
type: 'object',
description: 'Complete file metadata from Google Drive',
properties: {
// Basic Info
id: { type: 'string', description: 'Google Drive file ID' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
kind: { type: 'string', description: 'Resource type identifier' },
description: { type: 'string', description: 'File description' },
originalFilename: { type: 'string', description: 'Original uploaded filename' },
fullFileExtension: { type: 'string', description: 'Full file extension' },
fileExtension: { type: 'string', description: 'File extension' },
// Ownership & Sharing
owners: { type: 'json', description: 'List of file owners' },
permissions: { type: 'json', description: 'File permissions' },
permissionIds: { type: 'json', description: 'Permission IDs' },
shared: { type: 'boolean', description: 'Whether file 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 file' },
// Labels/Tags
starred: { type: 'boolean', description: 'Whether file is starred' },
trashed: { type: 'boolean', description: 'Whether file is in trash' },
explicitlyTrashed: { type: 'boolean', description: 'Whether explicitly trashed' },
properties: { type: 'json', description: 'Custom properties' },
appProperties: { type: 'json', description: 'App-specific properties' },
// Timestamps
createdTime: { type: 'string', description: 'File 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 file' },
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' },
webContentLink: { type: 'string', description: 'Direct download URL' },
iconLink: { type: 'string', description: 'URL to file icon' },
thumbnailLink: { type: 'string', description: 'URL to thumbnail' },
exportLinks: { type: 'json', description: 'Export format links' },
// Size & Storage
size: { type: 'string', description: 'File size in bytes' },
quotaBytesUsed: { type: 'string', description: 'Storage quota used' },
// Checksums
md5Checksum: { type: 'string', description: 'MD5 hash' },
sha1Checksum: { type: 'string', description: 'SHA-1 hash' },
sha256Checksum: { type: 'string', description: 'SHA-256 hash' },
// Hierarchy & Location
parents: { type: 'json', description: 'Parent folder IDs' },
spaces: { type: 'json', description: 'Spaces containing file' },
driveId: { type: 'string', description: 'Shared drive ID' },
// Capabilities
capabilities: { type: 'json', description: 'User capabilities on file' },
// Versions
version: { type: 'string', description: 'Version number' },
headRevisionId: { type: 'string', description: 'Head revision ID' },
// Media Metadata
hasThumbnail: { type: 'boolean', description: 'Whether has thumbnail' },
thumbnailVersion: { type: 'string', description: 'Thumbnail version' },
imageMediaMetadata: { type: 'json', description: 'Image-specific metadata' },
videoMediaMetadata: { type: 'json', description: 'Video-specific metadata' },
// Other
isAppAuthorized: { type: 'boolean', description: 'Whether created by requesting app' },
contentRestrictions: { type: 'json', description: 'Content restrictions' },
linkShareMetadata: { type: 'json', description: 'Link share metadata' },
// Revisions
revisions: {
type: 'json',
description: 'File revision history (first 100 revisions only)',
},
},
},
},
}

View File

@@ -1,9 +1,16 @@
import { createLogger } from '@sim/logger'
import type {
GoogleDriveFile,
GoogleDriveGetContentResponse,
GoogleDriveRevision,
GoogleDriveToolParams,
} from '@/tools/google_drive/types'
import { DEFAULT_EXPORT_FORMATS, GOOGLE_WORKSPACE_MIME_TYPES } from '@/tools/google_drive/utils'
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('GoogleDriveGetContentTool')
@@ -12,7 +19,7 @@ export const getContentTool: ToolConfig<GoogleDriveToolParams, GoogleDriveGetCon
id: 'google_drive_get_content',
name: 'Get Content from Google Drive',
description:
'Get content from a file in Google Drive (exports Google Workspace files automatically)',
'Get content from a file in Google Drive with complete metadata (exports Google Workspace files automatically)',
version: '1.0',
oauth: {
@@ -39,11 +46,18 @@ export const getContentTool: ToolConfig<GoogleDriveToolParams, GoogleDriveGetCon
visibility: 'hidden',
description: 'The MIME type to export Google Workspace files to (optional)',
},
includeRevisions: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
'Whether to include revision history in the metadata (default: true, returns first 100 revisions)',
},
},
request: {
url: (params) =>
`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<GoogleDriveToolParams, GoogleDriveGetCon
throw new Error(errorDetails.error?.message || 'Failed to get file metadata')
}
const metadata = await response.json()
const metadata: GoogleDriveFile = await response.json()
const fileId = metadata.id
const mimeType = metadata.mimeType
const authHeader = `Bearer ${params?.accessToken || ''}`
@@ -124,40 +138,58 @@ export const getContentTool: ToolConfig<GoogleDriveToolParams, GoogleDriveGetCon
content = await downloadResponse.text()
}
const metadataResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}?fields=id,name,mimeType,webViewLink,webContentLink,size,createdTime,modifiedTime,parents&supportsAllDrives=true`,
{
headers: {
Authorization: authHeader,
},
}
)
const includeRevisions = params?.includeRevisions !== false
const canReadRevisions = metadata.capabilities?.canReadRevisions === true
if (includeRevisions && canReadRevisions) {
try {
const revisionsResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}/revisions?fields=revisions(${ALL_REVISION_FIELDS})&pageSize=100`,
{
headers: {
Authorization: authHeader,
},
}
)
if (!metadataResponse.ok) {
logger.warn('Failed to get full metadata, using partial metadata', {
status: metadataResponse.status,
statusText: metadataResponse.statusText,
if (revisionsResponse.ok) {
const revisionsData = await revisionsResponse.json()
metadata.revisions = revisionsData.revisions as GoogleDriveRevision[]
logger.info('Fetched file revisions', {
fileId,
revisionCount: metadata.revisions?.length || 0,
})
} else {
logger.warn('Failed to fetch revisions, continuing without them', {
status: revisionsResponse.status,
statusText: revisionsResponse.statusText,
})
}
} catch (revisionError: any) {
logger.warn('Error fetching revisions, continuing without them', {
error: revisionError.message,
})
}
} else if (includeRevisions && !canReadRevisions) {
logger.info('Skipping revision fetch - user does not have canReadRevisions permission', {
fileId,
})
} else {
const fullMetadata = await metadataResponse.json()
Object.assign(metadata, fullMetadata)
}
logger.info('File content retrieved successfully', {
fileId,
name: metadata.name,
mimeType: metadata.mimeType,
contentLength: content.length,
hasOwners: !!metadata.owners?.length,
hasPermissions: !!metadata.permissions?.length,
hasRevisions: !!metadata.revisions?.length,
})
return {
success: true,
output: {
content,
metadata: {
id: metadata.id,
name: metadata.name,
mimeType: metadata.mimeType,
webViewLink: metadata.webViewLink,
webContentLink: metadata.webContentLink,
size: metadata.size,
createdTime: metadata.createdTime,
modifiedTime: metadata.modifiedTime,
parents: metadata.parents,
},
metadata,
},
}
} catch (error: any) {
@@ -175,8 +207,84 @@ export const getContentTool: ToolConfig<GoogleDriveToolParams, GoogleDriveGetCon
description: 'File content as text (Google Workspace files are exported)',
},
metadata: {
type: 'json',
description: 'File metadata including ID, name, MIME type, and links',
type: 'object',
description: 'Complete file metadata from Google Drive',
properties: {
// Basic Info
id: { type: 'string', description: 'Google Drive file ID' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
kind: { type: 'string', description: 'Resource type identifier' },
description: { type: 'string', description: 'File description' },
originalFilename: { type: 'string', description: 'Original uploaded filename' },
fullFileExtension: { type: 'string', description: 'Full file extension' },
fileExtension: { type: 'string', description: 'File extension' },
// Ownership & Sharing
owners: { type: 'json', description: 'List of file owners' },
permissions: { type: 'json', description: 'File permissions' },
permissionIds: { type: 'json', description: 'Permission IDs' },
shared: { type: 'boolean', description: 'Whether file 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 file' },
// Labels/Tags
starred: { type: 'boolean', description: 'Whether file is starred' },
trashed: { type: 'boolean', description: 'Whether file is in trash' },
explicitlyTrashed: { type: 'boolean', description: 'Whether explicitly trashed' },
properties: { type: 'json', description: 'Custom properties' },
appProperties: { type: 'json', description: 'App-specific properties' },
// Timestamps
createdTime: { type: 'string', description: 'File 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 file' },
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' },
webContentLink: { type: 'string', description: 'Direct download URL' },
iconLink: { type: 'string', description: 'URL to file icon' },
thumbnailLink: { type: 'string', description: 'URL to thumbnail' },
exportLinks: { type: 'json', description: 'Export format links' },
// Size & Storage
size: { type: 'string', description: 'File size in bytes' },
quotaBytesUsed: { type: 'string', description: 'Storage quota used' },
// Checksums
md5Checksum: { type: 'string', description: 'MD5 hash' },
sha1Checksum: { type: 'string', description: 'SHA-1 hash' },
sha256Checksum: { type: 'string', description: 'SHA-256 hash' },
// Hierarchy & Location
parents: { type: 'json', description: 'Parent folder IDs' },
spaces: { type: 'json', description: 'Spaces containing file' },
driveId: { type: 'string', description: 'Shared drive ID' },
// Capabilities
capabilities: { type: 'json', description: 'User capabilities on file' },
// Versions
version: { type: 'string', description: 'Version number' },
headRevisionId: { type: 'string', description: 'Head revision ID' },
// Media Metadata
hasThumbnail: { type: 'boolean', description: 'Whether has thumbnail' },
thumbnailVersion: { type: 'string', description: 'Thumbnail version' },
imageMediaMetadata: { type: 'json', description: 'Image-specific metadata' },
videoMediaMetadata: { type: 'json', description: 'Video-specific metadata' },
// Other
isAppAuthorized: { type: 'boolean', description: 'Whether created by requesting app' },
contentRestrictions: { type: 'json', description: 'Content restrictions' },
linkShareMetadata: { type: 'json', description: 'Link share metadata' },
// Revisions
revisions: {
type: 'json',
description: 'File revision history (first 100 revisions only)',
},
},
},
},
}

View File

@@ -1,10 +1,11 @@
import type { GoogleDriveListResponse, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig } from '@/tools/types'
export const listTool: ToolConfig<GoogleDriveToolParams, GoogleDriveListResponse> = {
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<GoogleDriveToolParams, GoogleDriveListResponse
request: {
url: (params) => {
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<GoogleDriveToolParams, GoogleDriveListResponse
if (params.query) {
const existingQ = url.searchParams.get('q')
const queryPart = `name contains '${params.query}'`
const escapedQuery = escapeQueryValue(params.query)
const queryPart = `name contains '${escapedQuery}'`
url.searchParams.set('q', `${existingQ} and ${queryPart}`)
}
if (params.pageSize) {
@@ -104,17 +108,7 @@ export const listTool: ToolConfig<GoogleDriveToolParams, GoogleDriveListResponse
return {
success: true,
output: {
files: data.files.map((file: any) => ({
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<GoogleDriveToolParams, GoogleDriveListResponse
outputs: {
files: {
type: 'json',
description: 'Array of file metadata objects from the specified folder',
type: 'array',
description: 'Array of file metadata objects from Google Drive',
items: {
type: 'object',
properties: {
// Basic Info
id: { type: 'string', description: 'Google Drive file ID' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
kind: { type: 'string', description: 'Resource type identifier' },
description: { type: 'string', description: 'File description' },
originalFilename: { type: 'string', description: 'Original uploaded filename' },
fullFileExtension: { type: 'string', description: 'Full file extension' },
fileExtension: { type: 'string', description: 'File extension' },
// Ownership & Sharing
owners: { type: 'json', description: 'List of file owners' },
permissions: { type: 'json', description: 'File permissions' },
permissionIds: { type: 'json', description: 'Permission IDs' },
shared: { type: 'boolean', description: 'Whether file 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 file' },
// Labels/Tags
starred: { type: 'boolean', description: 'Whether file is starred' },
trashed: { type: 'boolean', description: 'Whether file is in trash' },
explicitlyTrashed: { type: 'boolean', description: 'Whether explicitly trashed' },
properties: { type: 'json', description: 'Custom properties' },
appProperties: { type: 'json', description: 'App-specific properties' },
// Timestamps
createdTime: { type: 'string', description: 'File 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 file' },
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' },
webContentLink: { type: 'string', description: 'Direct download URL' },
iconLink: { type: 'string', description: 'URL to file icon' },
thumbnailLink: { type: 'string', description: 'URL to thumbnail' },
exportLinks: { type: 'json', description: 'Export format links' },
// Size & Storage
size: { type: 'string', description: 'File size in bytes' },
quotaBytesUsed: { type: 'string', description: 'Storage quota used' },
// Checksums
md5Checksum: { type: 'string', description: 'MD5 hash' },
sha1Checksum: { type: 'string', description: 'SHA-1 hash' },
sha256Checksum: { type: 'string', description: 'SHA-256 hash' },
// Hierarchy & Location
parents: { type: 'json', description: 'Parent folder IDs' },
spaces: { type: 'json', description: 'Spaces containing file' },
driveId: { type: 'string', description: 'Shared drive ID' },
// Capabilities
capabilities: { type: 'json', description: 'User capabilities on file' },
// Versions
version: { type: 'string', description: 'Version number' },
headRevisionId: { type: 'string', description: 'Head revision ID' },
// Media Metadata
hasThumbnail: { type: 'boolean', description: 'Whether has thumbnail' },
thumbnailVersion: { type: 'string', description: 'Thumbnail version' },
imageMediaMetadata: { type: 'json', description: 'Image-specific metadata' },
videoMediaMetadata: { type: 'json', description: 'Video-specific metadata' },
// Other
isAppAuthorized: { type: 'boolean', description: 'Whether created by requesting app' },
contentRestrictions: { type: 'json', description: 'Content restrictions' },
linkShareMetadata: { type: 'json', description: 'Link share metadata' },
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of results',
},
},
}

View File

@@ -1,15 +1,282 @@
import type { ToolResponse } from '@/tools/types'
// User information returned in various file metadata fields
export interface GoogleDriveUser {
displayName?: string
emailAddress?: string
photoLink?: string
permissionId?: string
kind?: string
me?: boolean
}
// Permission details for a file
export interface GoogleDrivePermission {
id?: string
type?: string // 'user' | 'group' | 'domain' | 'anyone'
role?: string // 'owner' | 'organizer' | 'fileOrganizer' | 'writer' | 'commenter' | 'reader'
emailAddress?: string
displayName?: string
photoLink?: string
domain?: string
expirationTime?: string
deleted?: boolean
allowFileDiscovery?: boolean
pendingOwner?: boolean
permissionDetails?: Array<{
permissionType?: string
role?: string
inheritedFrom?: string
inherited?: boolean
}>
}
// 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<string, string>
kind?: string
}
// Complete file metadata - all 50+ fields from Google Drive API v3
export interface GoogleDriveFile {
// Basic Info
id: string
name: string
mimeType: string
webViewLink?: string
webContentLink?: string
size?: 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<string, string>
appProperties?: Record<string, string>
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<string, string>
// Size & Storage
size?: 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 =

View File

@@ -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<GoogleDriveToolParams, GoogleDriveUploadResponse> = {
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<GoogleDriveToolParams, GoogleDriveUploadResp
}
}
// Fetch complete file metadata with all fields
const finalFileResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}?supportsAllDrives=true&fields=id,name,mimeType,webViewLink,webContentLink,size,createdTime,modifiedTime,parents`,
`https://www.googleapis.com/drive/v3/files/${fileId}?supportsAllDrives=true&fields=${ALL_FILE_FIELDS}`,
{
headers: {
Authorization: authHeader,
@@ -242,17 +244,7 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
return {
success: true,
output: {
file: {
id: finalFile.id,
name: finalFile.name,
mimeType: finalFile.mimeType,
webViewLink: finalFile.webViewLink,
webContentLink: finalFile.webContentLink,
size: finalFile.size,
createdTime: finalFile.createdTime,
modifiedTime: finalFile.modifiedTime,
parents: finalFile.parents,
},
file: finalFile,
},
}
} catch (error: any) {
@@ -265,6 +257,80 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
},
outputs: {
file: { type: 'json', description: 'Uploaded file metadata including ID, name, and links' },
file: {
type: 'object',
description: 'Complete uploaded file metadata from Google Drive',
properties: {
// Basic Info
id: { type: 'string', description: 'Google Drive file ID' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
kind: { type: 'string', description: 'Resource type identifier' },
description: { type: 'string', description: 'File description' },
originalFilename: { type: 'string', description: 'Original uploaded filename' },
fullFileExtension: { type: 'string', description: 'Full file extension' },
fileExtension: { type: 'string', description: 'File extension' },
// Ownership & Sharing
owners: { type: 'json', description: 'List of file owners' },
permissions: { type: 'json', description: 'File permissions' },
permissionIds: { type: 'json', description: 'Permission IDs' },
shared: { type: 'boolean', description: 'Whether file 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 file' },
// Labels/Tags
starred: { type: 'boolean', description: 'Whether file is starred' },
trashed: { type: 'boolean', description: 'Whether file is in trash' },
explicitlyTrashed: { type: 'boolean', description: 'Whether explicitly trashed' },
properties: { type: 'json', description: 'Custom properties' },
appProperties: { type: 'json', description: 'App-specific properties' },
// Timestamps
createdTime: { type: 'string', description: 'File 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 file' },
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' },
webContentLink: { type: 'string', description: 'Direct download URL' },
iconLink: { type: 'string', description: 'URL to file icon' },
thumbnailLink: { type: 'string', description: 'URL to thumbnail' },
exportLinks: { type: 'json', description: 'Export format links' },
// Size & Storage
size: { type: 'string', description: 'File size in bytes' },
quotaBytesUsed: { type: 'string', description: 'Storage quota used' },
// Checksums
md5Checksum: { type: 'string', description: 'MD5 hash' },
sha1Checksum: { type: 'string', description: 'SHA-1 hash' },
sha256Checksum: { type: 'string', description: 'SHA-256 hash' },
// Hierarchy & Location
parents: { type: 'json', description: 'Parent folder IDs' },
spaces: { type: 'json', description: 'Spaces containing file' },
driveId: { type: 'string', description: 'Shared drive ID' },
// Capabilities
capabilities: { type: 'json', description: 'User capabilities on file' },
// Versions
version: { type: 'string', description: 'Version number' },
headRevisionId: { type: 'string', description: 'Head revision ID' },
// Media Metadata
hasThumbnail: { type: 'boolean', description: 'Whether has thumbnail' },
thumbnailVersion: { type: 'string', description: 'Thumbnail version' },
imageMediaMetadata: { type: 'json', description: 'Image-specific metadata' },
videoMediaMetadata: { type: 'json', description: 'Video-specific metadata' },
// Other
isAppAuthorized: { type: 'boolean', description: 'Whether created by requesting app' },
contentRestrictions: { type: 'json', description: 'Content restrictions' },
linkShareMetadata: { type: 'json', description: 'Link share metadata' },
},
},
},
}

View File

@@ -1,3 +1,98 @@
// All available file metadata fields from Google Drive API v3
export const ALL_FILE_FIELDS = [
// Basic Info
'id',
'name',
'mimeType',
'kind',
'description',
'originalFilename',
'fullFileExtension',
'fileExtension',
// Ownership & Sharing
'owners',
'permissions',
'permissionIds',
'shared',
'ownedByMe',
'writersCanShare',
'viewersCanCopyContent',
'copyRequiresWriterPermission',
'sharingUser',
// Labels/Tags
'starred',
'trashed',
'explicitlyTrashed',
'properties',
'appProperties',
'folderColorRgb',
// Timestamps
'createdTime',
'modifiedTime',
'modifiedByMeTime',
'viewedByMeTime',
'sharedWithMeTime',
'trashedTime',
// User Info
'lastModifyingUser',
'trashingUser',
'viewedByMe',
'modifiedByMe',
// Links
'webViewLink',
'webContentLink',
'iconLink',
'thumbnailLink',
'exportLinks',
// Size & Storage
'size',
'quotaBytesUsed',
// Checksums
'md5Checksum',
'sha1Checksum',
'sha256Checksum',
// Hierarchy & Location
'parents',
'spaces',
'driveId',
'teamDriveId',
// Capabilities
'capabilities',
// Versions
'version',
'headRevisionId',
// Media Metadata
'hasThumbnail',
'thumbnailVersion',
'imageMediaMetadata',
'videoMediaMetadata',
'contentHints',
// Other
'isAppAuthorized',
'contentRestrictions',
'resourceKey',
'shortcutDetails',
'linkShareMetadata',
].join(',')
// All revision fields from Google Drive API v3
export const ALL_REVISION_FIELDS = [
'id',
'mimeType',
'modifiedTime',
'keepForever',
'published',
'publishAuto',
'publishedLink',
'publishedOutsideDomain',
'lastModifyingUser',
'originalFilename',
'md5Checksum',
'size',
'exportLinks',
'kind',
].join(',')
export const GOOGLE_WORKSPACE_MIME_TYPES = [
'application/vnd.google-apps.document', // Google Docs
'application/vnd.google-apps.spreadsheet', // Google Sheets