chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveCopyParams extends GoogleDriveToolParams {
fileId: string
newName?: string
destinationFolderId?: string
}
interface GoogleDriveCopyResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const copyTool: ToolConfig<GoogleDriveCopyParams, GoogleDriveCopyResponse> = {
id: 'google_drive_copy',
name: 'Copy Google Drive File',
description: 'Create a copy of a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to copy',
},
newName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Name for the copied file (defaults to "Copy of [original name]")',
},
destinationFolderId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ID of the folder to place the copy in (defaults to same location as original)',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/copy`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.newName) {
body.name = params.newName
}
if (params.destinationFolderId) {
body.parents = [params.destinationFolderId.trim()]
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json().catch(() => ({}) as any)
if (!response.ok) {
throw new Error(
data.error?.message ||
`Failed to copy Google Drive file (${response.status} ${response.statusText})`
)
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The copied file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID of the copy' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
parents: { type: 'json', description: 'Parent folder IDs' },
createdTime: { type: 'string', description: 'File creation time' },
modifiedTime: { type: 'string', description: 'Last modification time' },
owners: { type: 'json', description: 'List of file owners' },
size: { type: 'string', description: 'File size in bytes' },
},
},
},
}
@@ -0,0 +1,112 @@
import type { GoogleDriveComment, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_COMMENT_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveCreateCommentParams extends GoogleDriveToolParams {
fileId: string
content: string
anchor?: string
}
interface GoogleDriveCreateCommentResponse extends ToolResponse {
output: {
comment: GoogleDriveComment
}
}
export const createCommentTool: ToolConfig<
GoogleDriveCreateCommentParams,
GoogleDriveCreateCommentResponse
> = {
id: 'google_drive_create_comment',
name: 'Create Google Drive Comment',
description: 'Add a comment to a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to comment on',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The plain text content of the comment',
},
anchor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'A region of the document the comment refers to (JSON anchor string)',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/comments`
)
url.searchParams.append('fields', ALL_COMMENT_FIELDS)
return url.toString()
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
content: params.content,
}
if (params.anchor) {
body.anchor = params.anchor
}
return body
},
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as GoogleDriveComment
return {
success: true,
output: {
comment: data,
},
}
},
outputs: {
comment: {
type: 'json',
description: 'The created comment',
properties: {
id: { type: 'string', description: 'Comment ID' },
content: { type: 'string', description: 'Plain text content of the comment' },
htmlContent: { type: 'string', description: 'HTML-formatted content of the comment' },
author: { type: 'json', description: 'User who authored the comment' },
createdTime: { type: 'string', description: 'When the comment was created' },
modifiedTime: { type: 'string', description: 'When the comment was last modified' },
resolved: { type: 'boolean', description: 'Whether the comment has been resolved' },
deleted: { type: 'boolean', description: 'Whether the comment has been deleted' },
anchor: { type: 'string', description: 'Region of the document the comment refers to' },
quotedFileContent: { type: 'json', description: 'The file content the comment quotes' },
replies: { type: 'json', description: 'Threaded replies to the comment' },
},
},
},
}
@@ -0,0 +1,189 @@
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 with complete metadata returned',
version: '1.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Drive API',
},
fileName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the folder to create',
},
folderSelector: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Google Drive parent folder ID to create the folder in (e.g., 1ABCxyz...)',
},
folderId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'ID of the parent folder (internal use)',
},
},
request: {
url: 'https://www.googleapis.com/drive/v3/files?supportsAllDrives=true',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const metadata: {
name: string | undefined
mimeType: string
parents?: string[]
} = {
name: params.fileName,
mimeType: 'application/vnd.google-apps.folder',
}
// Add parent folder if specified (prefer folderSelector over folderId)
const parentFolderId = (params.folderSelector || params.folderId)?.trim()
if (parentFolderId) {
metadata.parents = [parentFolderId.trim()]
}
return metadata
},
},
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: fullMetadata,
},
}
},
outputs: {
file: {
type: 'object',
description: 'Complete created folder metadata from Google Drive',
properties: {
// Basic Info
id: { type: 'string', description: 'Google Drive folder ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'Folder name' },
mimeType: { type: 'string', description: 'MIME type (application/vnd.google-apps.folder)' },
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' },
},
},
},
}
+81
View File
@@ -0,0 +1,81 @@
import type { GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveDeleteParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveDeleteResponse extends ToolResponse {
output: {
deleted: boolean
fileId: string
}
}
export const deleteTool: ToolConfig<GoogleDriveDeleteParams, GoogleDriveDeleteResponse> = {
id: 'google_drive_delete',
name: 'Delete Google Drive File',
description: 'Permanently delete a file from Google Drive (bypasses trash)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to permanently delete',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response, params) => {
if (!response.ok) {
const data = await response.json().catch(() => ({}) as any)
throw new Error(
data.error?.message ||
`Failed to delete Google Drive file (${response.status} ${response.statusText})`
)
}
return {
success: true,
output: {
deleted: true,
fileId: params?.fileId ?? '',
},
}
},
outputs: {
deleted: {
type: 'boolean',
description: 'Whether the file was successfully deleted',
},
fileId: {
type: 'string',
description: 'The ID of the deleted file',
},
},
}
@@ -0,0 +1,75 @@
import type { GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveDeleteCommentParams extends GoogleDriveToolParams {
fileId: string
commentId: string
}
interface GoogleDriveDeleteCommentResponse extends ToolResponse {
output: {
deleted: boolean
fileId: string
commentId: string
}
}
export const deleteCommentTool: ToolConfig<
GoogleDriveDeleteCommentParams,
GoogleDriveDeleteCommentResponse
> = {
id: 'google_drive_delete_comment',
name: 'Delete Google Drive Comment',
description: 'Delete a comment from a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file the comment belongs to',
},
commentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the comment to delete',
},
},
request: {
url: (params) =>
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/comments/${params.commentId?.trim()}`,
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (_response: Response, params) => ({
success: true,
output: {
deleted: true,
fileId: params?.fileId ?? '',
commentId: params?.commentId ?? '',
},
}),
outputs: {
deleted: { type: 'boolean', description: 'Whether the comment was successfully deleted' },
fileId: { type: 'string', description: 'The ID of the file' },
commentId: { type: 'string', description: 'The ID of the deleted comment' },
},
}
+151
View File
@@ -0,0 +1,151 @@
import type { GoogleDriveDownloadResponse, GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig } from '@/tools/types'
export const downloadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveDownloadResponse> = {
id: 'google_drive_download',
name: 'Download File from Google Drive',
description:
'Download a file from Google Drive with complete metadata (exports Google Workspace files automatically)',
version: '1.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Drive API',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to download',
},
mimeType: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The MIME type to export Google Workspace files to (optional)',
},
fileName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
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: '/api/tools/google_drive/download',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
accessToken: params.accessToken,
fileId: params.fileId,
mimeType: params.mimeType,
fileName: params.fileName,
includeRevisions: params.includeRevisions,
}),
},
outputs: {
file: {
type: 'file',
description: 'Downloaded file stored in execution files',
},
metadata: {
type: 'object',
description: 'Complete file metadata from Google Drive',
properties: {
// Basic Info
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
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)',
},
},
},
},
}
+85
View File
@@ -0,0 +1,85 @@
import type { GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveExportParams extends GoogleDriveToolParams {
fileId: string
mimeType: string
fileName?: string
}
interface GoogleDriveExportResponse extends ToolResponse {
output: {
file: {
name: string
mimeType: string
data: string
size: number
}
exportedMimeType: string
}
}
export const exportTool: ToolConfig<GoogleDriveExportParams, GoogleDriveExportResponse> = {
id: 'google_drive_export',
name: 'Export Google Drive File',
description:
'Export a Google Workspace file (Docs, Sheets, Slides, Drawings) to a chosen format such as PDF, DOCX, XLSX, or CSV',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Drive API',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the Google Workspace file to export',
},
mimeType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The target MIME type to export to (e.g. application/pdf, text/csv)',
},
fileName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional filename override for the exported file',
},
},
request: {
url: '/api/tools/google_drive/export',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
accessToken: params.accessToken,
fileId: params.fileId,
mimeType: params.mimeType,
fileName: params.fileName,
}),
},
outputs: {
file: {
type: 'file',
description: 'Exported file stored in execution files',
},
exportedMimeType: {
type: 'string',
description: 'The MIME type the file was exported to',
},
},
}
+137
View File
@@ -0,0 +1,137 @@
import type { GoogleDriveToolParams, GoogleDriveUser } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveGetAboutParams extends GoogleDriveToolParams {}
interface GoogleDriveGetAboutResponse extends ToolResponse {
output: {
user: GoogleDriveUser & {
emailAddress: string
}
storageQuota: {
limit: string | null
usage: string
usageInDrive: string
usageInDriveTrash: string
}
canCreateDrives: boolean
importFormats: Record<string, string[]>
exportFormats: Record<string, string[]>
maxUploadSize: string
}
}
export const getAboutTool: ToolConfig<GoogleDriveGetAboutParams, GoogleDriveGetAboutResponse> = {
id: 'google_drive_get_about',
name: 'Get Google Drive Info',
description:
'Get information about the user and their Google Drive (storage quota, capabilities)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
},
request: {
url: () => {
const url = new URL('https://www.googleapis.com/drive/v3/about')
url.searchParams.append(
'fields',
'user,storageQuota,canCreateDrives,importFormats,exportFormats,maxUploadSize'
)
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to get Google Drive info')
}
return {
success: true,
output: {
user: {
displayName: data.user?.displayName ?? null,
emailAddress: data.user?.emailAddress ?? '',
photoLink: data.user?.photoLink ?? null,
permissionId: data.user?.permissionId ?? null,
me: data.user?.me ?? true,
},
storageQuota: {
limit: data.storageQuota?.limit ?? null,
usage: data.storageQuota?.usage ?? '0',
usageInDrive: data.storageQuota?.usageInDrive ?? '0',
usageInDriveTrash: data.storageQuota?.usageInDriveTrash ?? '0',
},
canCreateDrives: data.canCreateDrives ?? false,
importFormats: data.importFormats ?? {},
exportFormats: data.exportFormats ?? {},
maxUploadSize: data.maxUploadSize ?? '0',
},
}
},
outputs: {
user: {
type: 'json',
description: 'Information about the authenticated user',
properties: {
displayName: { type: 'string', description: 'User display name' },
emailAddress: { type: 'string', description: 'User email address' },
photoLink: { type: 'string', description: 'URL to user profile photo', optional: true },
permissionId: { type: 'string', description: 'User permission ID' },
me: { type: 'boolean', description: 'Whether this is the authenticated user' },
},
},
storageQuota: {
type: 'json',
description: 'Storage quota information in bytes',
properties: {
limit: {
type: 'string',
description: 'Total storage limit in bytes (null for unlimited)',
optional: true,
},
usage: { type: 'string', description: 'Total storage used in bytes' },
usageInDrive: { type: 'string', description: 'Storage used by Drive files in bytes' },
usageInDriveTrash: {
type: 'string',
description: 'Storage used by trashed files in bytes',
},
},
},
canCreateDrives: {
type: 'boolean',
description: 'Whether user can create shared drives',
},
importFormats: {
type: 'json',
description: 'Map of MIME types that can be imported and their target formats',
},
exportFormats: {
type: 'json',
description: 'Map of Google Workspace MIME types and their exportable formats',
},
maxUploadSize: {
type: 'string',
description: 'Maximum upload size in bytes',
},
},
}
+290
View File
@@ -0,0 +1,290 @@
import { createLogger } from '@sim/logger'
import type {
GoogleDriveFile,
GoogleDriveGetContentResponse,
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('GoogleDriveGetContentTool')
export const getContentTool: ToolConfig<GoogleDriveToolParams, GoogleDriveGetContentResponse> = {
id: 'google_drive_get_content',
name: 'Get Content from Google Drive',
description:
'Get content from a file in Google Drive with complete metadata (exports Google Workspace files automatically)',
version: '1.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Drive API',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to get content from',
},
mimeType: {
type: 'string',
required: false,
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?.trim()}?fields=${ALL_FILE_FIELDS}&supportsAllDrives=true`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response, params?: GoogleDriveToolParams) => {
try {
if (!response.ok) {
const errorDetails = await response.json().catch(() => ({}))
logger.error('Failed to get file metadata', {
status: response.status,
statusText: response.statusText,
error: errorDetails,
})
throw new Error(errorDetails.error?.message || 'Failed to get file metadata')
}
const metadata: GoogleDriveFile = await response.json()
const fileId = metadata.id
const mimeType = metadata.mimeType
const authHeader = `Bearer ${params?.accessToken || ''}`
let content: string
if (GOOGLE_WORKSPACE_MIME_TYPES.includes(mimeType)) {
const exportFormat = params?.mimeType || DEFAULT_EXPORT_FORMATS[mimeType] || 'text/plain'
logger.info('Exporting Google Workspace file', {
fileId,
mimeType,
exportFormat,
})
const exportResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}/export?mimeType=${encodeURIComponent(exportFormat)}&supportsAllDrives=true`,
{
headers: {
Authorization: authHeader,
},
}
)
if (!exportResponse.ok) {
const exportError = await exportResponse.json().catch(() => ({}))
logger.error('Failed to export file', {
status: exportResponse.status,
statusText: exportResponse.statusText,
error: exportError,
})
throw new Error(exportError.error?.message || 'Failed to export Google Workspace file')
}
content = await exportResponse.text()
} else {
logger.info('Downloading regular file', {
fileId,
mimeType,
})
const downloadResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}?alt=media&supportsAllDrives=true`,
{
headers: {
Authorization: authHeader,
},
}
)
if (!downloadResponse.ok) {
const downloadError = await downloadResponse.json().catch(() => ({}))
logger.error('Failed to download file', {
status: downloadResponse.status,
statusText: downloadResponse.statusText,
error: downloadError,
})
throw new Error(downloadError.error?.message || 'Failed to download file')
}
content = await downloadResponse.text()
}
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,
})
}
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,
},
}
} catch (error: any) {
logger.error('Error in transform response', {
error: error.message,
stack: error.stack,
})
throw error
}
},
outputs: {
content: {
type: 'string',
description: 'File content as text (Google Workspace files are exported)',
},
metadata: {
type: 'object',
description: 'Complete file metadata from Google Drive',
properties: {
// Basic Info
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
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)',
},
},
},
},
}
+103
View File
@@ -0,0 +1,103 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveGetFileParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveGetFileResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const getFileTool: ToolConfig<GoogleDriveGetFileParams, GoogleDriveGetFileResponse> = {
id: 'google_drive_get_file',
name: 'Get Google Drive File',
description: 'Get metadata for a specific file in Google Drive by its ID',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to retrieve',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json().catch(() => ({}) as any)
if (!response.ok) {
throw new Error(
data.error?.message ||
`Failed to get Google Drive file (${response.status} ${response.statusText})`
)
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
description: { type: 'string', description: 'File description', optional: true },
size: { type: 'string', description: 'File size in bytes', optional: true },
starred: { type: 'boolean', description: 'Whether file is starred' },
trashed: { type: 'boolean', description: 'Whether file is in trash' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
webContentLink: { type: 'string', description: 'Direct download URL', optional: true },
iconLink: { type: 'string', description: 'URL to file icon' },
thumbnailLink: { type: 'string', description: 'URL to thumbnail', optional: true },
parents: { type: 'json', description: 'Parent folder IDs' },
owners: { type: 'json', description: 'List of file owners' },
permissions: { type: 'json', description: 'File permissions', optional: true },
createdTime: { type: 'string', description: 'File creation time' },
modifiedTime: { type: 'string', description: 'Last modification time' },
lastModifyingUser: { type: 'json', description: 'User who last modified the file' },
shared: { type: 'boolean', description: 'Whether file is shared' },
ownedByMe: { type: 'boolean', description: 'Whether owned by current user' },
capabilities: { type: 'json', description: 'User capabilities on file' },
md5Checksum: { type: 'string', description: 'MD5 hash', optional: true },
version: { type: 'string', description: 'Version number' },
},
},
},
}
@@ -0,0 +1,98 @@
import type { GoogleDriveRevision, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_REVISION_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveGetRevisionParams extends GoogleDriveToolParams {
fileId: string
revisionId: string
}
interface GoogleDriveGetRevisionResponse extends ToolResponse {
output: {
revision: GoogleDriveRevision
}
}
export const getRevisionTool: ToolConfig<
GoogleDriveGetRevisionParams,
GoogleDriveGetRevisionResponse
> = {
id: 'google_drive_get_revision',
name: 'Get Google Drive Revision',
description: 'Get metadata for a specific revision of a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file the revision belongs to',
},
revisionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the revision to retrieve',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/revisions/${params.revisionId?.trim()}`
)
url.searchParams.append('fields', ALL_REVISION_FIELDS)
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as GoogleDriveRevision
return {
success: true,
output: {
revision: data,
},
}
},
outputs: {
revision: {
type: 'json',
description: 'The revision metadata',
properties: {
id: { type: 'string', description: 'Revision ID' },
mimeType: { type: 'string', description: 'MIME type of the revision' },
modifiedTime: { type: 'string', description: 'When this revision was created' },
keepForever: {
type: 'boolean',
description: 'Whether this revision is preserved forever',
},
published: { type: 'boolean', description: 'Whether this revision is published' },
publishedLink: { type: 'string', description: 'Public link to the published revision' },
lastModifyingUser: { type: 'json', description: 'User who created this revision' },
originalFilename: { type: 'string', description: 'Original filename for binary revisions' },
md5Checksum: { type: 'string', description: 'MD5 checksum for binary revisions' },
size: { type: 'string', description: 'Size of the revision in bytes' },
exportLinks: { type: 'json', description: 'Export format links for the revision' },
},
},
},
}
+47
View File
@@ -0,0 +1,47 @@
import { copyTool } from '@/tools/google_drive/copy'
import { createCommentTool } from '@/tools/google_drive/create_comment'
import { createFolderTool } from '@/tools/google_drive/create_folder'
import { deleteTool } from '@/tools/google_drive/delete'
import { deleteCommentTool } from '@/tools/google_drive/delete_comment'
import { downloadTool } from '@/tools/google_drive/download'
import { exportTool } from '@/tools/google_drive/export'
import { getAboutTool } from '@/tools/google_drive/get_about'
import { getContentTool } from '@/tools/google_drive/get_content'
import { getFileTool } from '@/tools/google_drive/get_file'
import { getRevisionTool } from '@/tools/google_drive/get_revision'
import { listTool } from '@/tools/google_drive/list'
import { listCommentsTool } from '@/tools/google_drive/list_comments'
import { listPermissionsTool } from '@/tools/google_drive/list_permissions'
import { listRevisionsTool } from '@/tools/google_drive/list_revisions'
import { moveTool } from '@/tools/google_drive/move'
import { searchTool } from '@/tools/google_drive/search'
import { shareTool } from '@/tools/google_drive/share'
import { trashTool } from '@/tools/google_drive/trash'
import { unshareTool } from '@/tools/google_drive/unshare'
import { untrashTool } from '@/tools/google_drive/untrash'
import { updateTool } from '@/tools/google_drive/update'
import { uploadTool } from '@/tools/google_drive/upload'
export const googleDriveCopyTool = copyTool
export const googleDriveCreateCommentTool = createCommentTool
export const googleDriveCreateFolderTool = createFolderTool
export const googleDriveDeleteTool = deleteTool
export const googleDriveDeleteCommentTool = deleteCommentTool
export const googleDriveDownloadTool = downloadTool
export const googleDriveExportTool = exportTool
export const googleDriveGetAboutTool = getAboutTool
export const googleDriveGetContentTool = getContentTool
export const googleDriveGetFileTool = getFileTool
export const googleDriveGetRevisionTool = getRevisionTool
export const googleDriveListTool = listTool
export const googleDriveListCommentsTool = listCommentsTool
export const googleDriveListPermissionsTool = listPermissionsTool
export const googleDriveListRevisionsTool = listRevisionsTool
export const googleDriveMoveTool = moveTool
export const googleDriveSearchTool = searchTool
export const googleDriveShareTool = shareTool
export const googleDriveTrashTool = trashTool
export const googleDriveUnshareTool = unshareTool
export const googleDriveUntrashTool = untrashTool
export const googleDriveUpdateTool = updateTool
export const googleDriveUploadTool = uploadTool
+204
View File
@@ -0,0 +1,204 @@
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 with complete metadata',
version: '1.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Drive API',
},
folderSelector: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Google Drive folder ID to list files from (e.g., 1ABCxyz...)',
},
folderId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The ID of the folder to list files from (internal use)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Search term to filter files by name (e.g. "budget" finds files with "budget" in the name). Do NOT use Google Drive query syntax here - just provide a plain search term.',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'The maximum number of files to return (default: 100)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The page token to use for pagination',
},
},
request: {
url: (params) => {
const url = new URL('https://www.googleapis.com/drive/v3/files')
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. `params.query` here is a plain-text name
// search term (wrapped in `name contains '...'` below), not Google Drive
// query syntax — so there's no caller-supplied `trashed` predicate to
// honour. Always exclude trashed files.
const conditions: string[] = ['trashed = false']
const folderId = (params.folderId || params.folderSelector)?.trim()
if (folderId) {
const escapedFolderId = escapeQueryValue(folderId)
conditions.push(`'${escapedFolderId}' in parents`)
}
// Combine all conditions with AND
url.searchParams.append('q', conditions.join(' and '))
if (params.query) {
const existingQ = url.searchParams.get('q')
const escapedQuery = escapeQueryValue(params.query)
const queryPart = `name contains '${escapedQuery}'`
url.searchParams.set('q', `${existingQ} and ${queryPart}`)
}
if (params.pageSize) {
url.searchParams.append('pageSize', Number(params.pageSize).toString())
}
if (params.pageToken) {
url.searchParams.append('pageToken', params.pageToken)
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to list Google Drive files')
}
return {
success: true,
output: {
files: data.files ?? [],
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
files: {
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' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
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',
},
},
}
@@ -0,0 +1,140 @@
import type { GoogleDriveComment, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_COMMENT_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveListCommentsParams extends GoogleDriveToolParams {
fileId: string
includeDeleted?: boolean
pageSize?: number
pageToken?: string
startModifiedTime?: string
}
interface GoogleDriveListCommentsResponse extends ToolResponse {
output: {
comments: GoogleDriveComment[]
nextPageToken?: string
}
}
export const listCommentsTool: ToolConfig<
GoogleDriveListCommentsParams,
GoogleDriveListCommentsResponse
> = {
id: 'google_drive_list_comments',
name: 'List Google Drive Comments',
description: 'List comments on a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to list comments for',
},
includeDeleted: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include deleted comments (their content is stripped)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of comments to return (1-100, default 20)',
},
startModifiedTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only return comments modified after this RFC 3339 timestamp',
},
pageToken: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The page token to use for pagination',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/comments`
)
url.searchParams.append('fields', `nextPageToken,comments(${ALL_COMMENT_FIELDS})`)
if (params.includeDeleted !== undefined) {
url.searchParams.append('includeDeleted', String(params.includeDeleted))
}
if (params.pageSize) {
url.searchParams.append('pageSize', String(params.pageSize))
}
if (params.startModifiedTime) {
url.searchParams.append('startModifiedTime', params.startModifiedTime)
}
if (params.pageToken) {
url.searchParams.append('pageToken', params.pageToken)
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
comments: (data.comments ?? []) as GoogleDriveComment[],
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
comments: {
type: 'array',
description: 'List of comments on the file',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Comment ID' },
content: { type: 'string', description: 'Plain text content of the comment' },
htmlContent: { type: 'string', description: 'HTML-formatted content of the comment' },
author: { type: 'json', description: 'User who authored the comment' },
createdTime: { type: 'string', description: 'When the comment was created' },
modifiedTime: { type: 'string', description: 'When the comment was last modified' },
resolved: { type: 'boolean', description: 'Whether the comment has been resolved' },
deleted: { type: 'boolean', description: 'Whether the comment has been deleted' },
anchor: { type: 'string', description: 'Region of the document the comment refers to' },
quotedFileContent: {
type: 'json',
description: 'The file content the comment quotes',
},
replies: { type: 'json', description: 'Threaded replies to the comment' },
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of comments',
},
},
}
@@ -0,0 +1,140 @@
import type { GoogleDrivePermission, GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveListPermissionsParams extends GoogleDriveToolParams {
fileId: string
pageToken?: string
}
interface GoogleDriveListPermissionsResponse extends ToolResponse {
output: {
permissions: GoogleDrivePermission[]
nextPageToken?: string
}
}
export const listPermissionsTool: ToolConfig<
GoogleDriveListPermissionsParams,
GoogleDriveListPermissionsResponse
> = {
id: 'google_drive_list_permissions',
name: 'List Google Drive Permissions',
description: 'List all permissions (who has access) for a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to list permissions for',
},
pageToken: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The page token to use for pagination',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/permissions`
)
url.searchParams.append('supportsAllDrives', 'true')
url.searchParams.append(
'fields',
'nextPageToken,permissions(id,type,role,emailAddress,displayName,photoLink,domain,expirationTime,deleted,allowFileDiscovery,pendingOwner,permissionDetails)'
)
if (params.pageToken) {
url.searchParams.append('pageToken', params.pageToken)
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to list Google Drive permissions')
}
const permissions = (data.permissions ?? []).map((p: Record<string, unknown>) => ({
id: p.id ?? null,
type: p.type ?? null,
role: p.role ?? null,
emailAddress: p.emailAddress ?? null,
displayName: p.displayName ?? null,
photoLink: p.photoLink ?? null,
domain: p.domain ?? null,
expirationTime: p.expirationTime ?? null,
deleted: p.deleted ?? false,
allowFileDiscovery: p.allowFileDiscovery ?? null,
pendingOwner: p.pendingOwner ?? false,
permissionDetails: p.permissionDetails ?? null,
}))
return {
success: true,
output: {
permissions,
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
permissions: {
type: 'array',
description: 'List of permissions on the file',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Permission ID (use to remove permission)' },
type: { type: 'string', description: 'Grantee type (user, group, domain, anyone)' },
role: {
type: 'string',
description:
'Permission role (owner, organizer, fileOrganizer, writer, commenter, reader)',
},
emailAddress: { type: 'string', description: 'Email of the grantee' },
displayName: { type: 'string', description: 'Display name of the grantee' },
photoLink: { type: 'string', description: 'Photo URL of the grantee' },
domain: { type: 'string', description: 'Domain of the grantee' },
expirationTime: { type: 'string', description: 'When permission expires' },
deleted: { type: 'boolean', description: 'Whether grantee account is deleted' },
allowFileDiscovery: {
type: 'boolean',
description: 'Whether file is discoverable by grantee',
},
pendingOwner: { type: 'boolean', description: 'Whether ownership transfer is pending' },
permissionDetails: {
type: 'json',
description: 'Details about inherited permissions',
},
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of permissions',
},
},
}
@@ -0,0 +1,126 @@
import type { GoogleDriveRevision, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_REVISION_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveListRevisionsParams extends GoogleDriveToolParams {
fileId: string
pageSize?: number
pageToken?: string
}
interface GoogleDriveListRevisionsResponse extends ToolResponse {
output: {
revisions: GoogleDriveRevision[]
nextPageToken?: string
}
}
export const listRevisionsTool: ToolConfig<
GoogleDriveListRevisionsParams,
GoogleDriveListRevisionsResponse
> = {
id: 'google_drive_list_revisions',
name: 'List Google Drive Revisions',
description: 'List the revision history of a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to list revisions for',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of revisions to return (1-1000, default 200)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The page token to use for pagination',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/revisions`
)
url.searchParams.append('fields', `nextPageToken,revisions(${ALL_REVISION_FIELDS})`)
if (params.pageSize) {
url.searchParams.append('pageSize', String(params.pageSize))
}
if (params.pageToken) {
url.searchParams.append('pageToken', params.pageToken)
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
revisions: (data.revisions ?? []) as GoogleDriveRevision[],
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
revisions: {
type: 'array',
description: 'List of revisions for the file (most recent last)',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Revision ID' },
mimeType: { type: 'string', description: 'MIME type of the revision' },
modifiedTime: { type: 'string', description: 'When this revision was created' },
keepForever: {
type: 'boolean',
description: 'Whether this revision is preserved forever',
},
published: { type: 'boolean', description: 'Whether this revision is published' },
publishedLink: { type: 'string', description: 'Public link to the published revision' },
lastModifyingUser: {
type: 'json',
description: 'User who created this revision',
},
originalFilename: {
type: 'string',
description: 'Original filename for binary revisions',
},
md5Checksum: { type: 'string', description: 'MD5 checksum for binary revisions' },
size: { type: 'string', description: 'Size of the revision in bytes' },
exportLinks: { type: 'json', description: 'Export format links for the revision' },
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of revisions',
},
},
}
+145
View File
@@ -0,0 +1,145 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveMoveParams extends GoogleDriveToolParams {
fileId: string
destinationFolderId: string
removeFromCurrent?: boolean
}
interface GoogleDriveMoveResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const moveTool: ToolConfig<GoogleDriveMoveParams, GoogleDriveMoveResponse> = {
id: 'google_drive_move',
name: 'Move Google Drive File',
description: 'Move a file or folder to a different folder in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file or folder to move',
},
destinationFolderId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the destination folder',
},
removeFromCurrent: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
'Whether to remove the file from its current parent folder (default: true). Set to false to add the file to the destination without removing it from the current location.',
},
},
request: {
url: 'https://www.googleapis.com/drive/v3/files',
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
directExecution: async (params) => {
const fileId = params.fileId?.trim()
const destinationFolderId = params.destinationFolderId?.trim()
const removeFromCurrent = params.removeFromCurrent !== false
if (!fileId) {
throw new Error('fileId is required')
}
if (!destinationFolderId) {
throw new Error('destinationFolderId is required')
}
const headers = {
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}
// Build the PATCH URL with addParents
const url = new URL(`https://www.googleapis.com/drive/v3/files/${fileId}`)
url.searchParams.append('addParents', destinationFolderId)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
if (removeFromCurrent) {
// Fetch current parents so we can remove them
const metadataUrl = new URL(`https://www.googleapis.com/drive/v3/files/${fileId}`)
metadataUrl.searchParams.append('fields', 'parents')
metadataUrl.searchParams.append('supportsAllDrives', 'true')
const metadataResponse = await fetch(metadataUrl.toString(), { headers })
if (!metadataResponse.ok) {
const errorData = await metadataResponse.json()
throw new Error(errorData.error?.message || 'Failed to retrieve file metadata')
}
const metadata = await metadataResponse.json()
if (metadata.parents && metadata.parents.length > 0) {
url.searchParams.append('removeParents', metadata.parents.join(','))
}
}
const response = await fetch(url.toString(), {
method: 'PATCH',
headers,
body: JSON.stringify({}),
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to move Google Drive file')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The moved file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
parents: { type: 'json', description: 'Parent folder IDs' },
createdTime: { type: 'string', description: 'File creation time' },
modifiedTime: { type: 'string', description: 'Last modification time' },
owners: { type: 'json', description: 'List of file owners' },
size: { type: 'string', description: 'File size in bytes' },
},
},
},
}
+150
View File
@@ -0,0 +1,150 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveSearchParams extends GoogleDriveToolParams {
query: string
pageSize?: number
pageToken?: string
}
interface GoogleDriveSearchResponse extends ToolResponse {
output: {
files: GoogleDriveFile[]
nextPageToken?: string
}
}
export const searchTool: ToolConfig<GoogleDriveSearchParams, GoogleDriveSearchResponse> = {
id: 'google_drive_search',
name: 'Search Google Drive Files',
description:
'Search for files in Google Drive using advanced query syntax (e.g., fullText contains, mimeType, modifiedTime, etc.)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Google Drive query string using advanced search syntax (e.g., "fullText contains \'budget\'", "mimeType = \'application/pdf\'", "modifiedTime > \'2024-01-01\'")',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of files to return (default: 100)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Token for fetching the next page of results',
},
},
request: {
url: (params) => {
const url = new URL('https://www.googleapis.com/drive/v3/files')
url.searchParams.append('fields', `files(${ALL_FILE_FIELDS}),nextPageToken`)
url.searchParams.append('corpora', 'allDrives')
url.searchParams.append('supportsAllDrives', 'true')
url.searchParams.append('includeItemsFromAllDrives', 'true')
// The query is passed directly as Google Drive query syntax
const userQuery = params.query?.trim()
const userSpecifiesTrashed = userQuery ? /\btrashed\s*=/.test(userQuery) : false
const conditions: string[] = []
if (!userSpecifiesTrashed) {
conditions.push('trashed = false')
}
if (userQuery) {
conditions.push(userQuery)
}
url.searchParams.append('q', conditions.join(' and '))
if (params.pageSize) {
url.searchParams.append('pageSize', Number(params.pageSize).toString())
}
if (params.pageToken) {
url.searchParams.append('pageToken', params.pageToken)
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to search Google Drive files')
}
return {
success: true,
output: {
files: data.files || [],
nextPageToken: data.nextPageToken,
},
}
},
outputs: {
files: {
type: 'array',
description: 'Array of file metadata objects matching the search query',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
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' },
owners: { type: 'json', description: 'List of file owners' },
permissions: { type: 'json', description: 'File permissions' },
shared: { type: 'boolean', description: 'Whether file is shared' },
ownedByMe: { type: 'boolean', description: 'Whether owned by current user' },
starred: { type: 'boolean', description: 'Whether file is starred' },
trashed: { type: 'boolean', description: 'Whether file is in trash' },
createdTime: { type: 'string', description: 'File creation time' },
modifiedTime: { type: 'string', description: 'Last modification time' },
lastModifyingUser: { type: 'json', description: 'User who last modified the file' },
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' },
size: { type: 'string', description: 'File size in bytes' },
parents: { type: 'json', description: 'Parent folder IDs' },
driveId: { type: 'string', description: 'Shared drive ID' },
capabilities: { type: 'json', description: 'User capabilities on file' },
version: { type: 'string', description: 'Version number' },
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for fetching the next page of results',
},
},
}
+180
View File
@@ -0,0 +1,180 @@
import type { GoogleDrivePermission, GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveShareParams extends GoogleDriveToolParams {
fileId: string
email?: string
domain?: string
type: 'user' | 'group' | 'domain' | 'anyone'
role: 'owner' | 'organizer' | 'fileOrganizer' | 'writer' | 'commenter' | 'reader'
transferOwnership?: boolean
moveToNewOwnersRoot?: boolean
sendNotification?: boolean
emailMessage?: string
}
interface GoogleDriveShareResponse extends ToolResponse {
output: {
permission: GoogleDrivePermission
}
}
export const shareTool: ToolConfig<GoogleDriveShareParams, GoogleDriveShareResponse> = {
id: 'google_drive_share',
name: 'Share Google Drive File',
description: 'Share a file with a user, group, domain, or make it public',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to share',
},
type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Type of grantee: user, group, domain, or anyone',
},
role: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Permission role: owner (transfer ownership), organizer (shared drive only), fileOrganizer (shared drive only), writer (edit), commenter (view and comment), reader (view only)',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address of the user or group (required for type=user or type=group)',
},
domain: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Domain to share with (required for type=domain)',
},
transferOwnership: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Required when role is owner. Transfers ownership to the specified user.',
},
moveToNewOwnersRoot: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
"When transferring ownership, move the file to the new owner's My Drive root folder.",
},
sendNotification: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to send an email notification (default: true)',
},
emailMessage: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Custom message to include in the notification email',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/permissions`
)
url.searchParams.append('supportsAllDrives', 'true')
if (params.transferOwnership) {
url.searchParams.append('transferOwnership', 'true')
}
if (params.transferOwnership && params.moveToNewOwnersRoot) {
url.searchParams.append('moveToNewOwnersRoot', 'true')
}
if (params.transferOwnership) {
url.searchParams.append('sendNotificationEmail', 'true')
} else if (params.sendNotification !== undefined) {
url.searchParams.append('sendNotificationEmail', String(params.sendNotification))
}
if (params.emailMessage) {
url.searchParams.append('emailMessage', params.emailMessage)
}
return url.toString()
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
type: params.type,
role: params.role,
}
if (params.email) {
body.emailAddress = params.email.trim()
}
if (params.domain) {
body.domain = params.domain.trim()
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to share Google Drive file')
}
return {
success: true,
output: {
permission: {
id: data.id ?? null,
type: data.type ?? null,
role: data.role ?? null,
emailAddress: data.emailAddress ?? null,
displayName: data.displayName ?? null,
domain: data.domain ?? null,
expirationTime: data.expirationTime ?? null,
deleted: data.deleted ?? false,
},
},
}
},
outputs: {
permission: {
type: 'json',
description: 'The created permission details',
properties: {
id: { type: 'string', description: 'Permission ID' },
type: { type: 'string', description: 'Grantee type (user, group, domain, anyone)' },
role: { type: 'string', description: 'Permission role' },
emailAddress: { type: 'string', description: 'Email of the grantee', optional: true },
displayName: { type: 'string', description: 'Display name of the grantee', optional: true },
domain: { type: 'string', description: 'Domain of the grantee', optional: true },
expirationTime: { type: 'string', description: 'Expiration time', optional: true },
deleted: { type: 'boolean', description: 'Whether grantee is deleted' },
},
},
},
}
+88
View File
@@ -0,0 +1,88 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveTrashParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveTrashResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const trashTool: ToolConfig<GoogleDriveTrashParams, GoogleDriveTrashResponse> = {
id: 'google_drive_trash',
name: 'Trash Google Drive File',
description: 'Move a file to the trash in Google Drive (can be restored later)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to move to trash',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: () => ({
trashed: true,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to trash Google Drive file')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The trashed file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
trashed: { type: 'boolean', description: 'Whether file is in trash (should be true)' },
trashedTime: { type: 'string', description: 'When file was trashed' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
},
},
},
}
+372
View File
@@ -0,0 +1,372 @@
import type { UserFile } from '@/executor/types'
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
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
interface GoogleDriveContentHints {
indexableText?: string
thumbnail?: {
image?: string
mimeType?: string
}
}
// Image-specific metadata
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
interface GoogleDriveVideoMediaMetadata {
width?: number
height?: number
durationMillis?: string
}
// Shortcut details
interface GoogleDriveShortcutDetails {
targetId?: string
targetMimeType?: string
targetResourceKey?: string
}
// Content restrictions
interface GoogleDriveContentRestriction {
readOnly?: boolean
reason?: string
type?: string
restrictingUser?: GoogleDriveUser
restrictionTime?: string
ownerRestricted?: boolean
systemRestricted?: boolean
}
// Link share metadata
interface GoogleDriveLinkShareMetadata {
securityUpdateEligible?: boolean
securityUpdateEnabled?: boolean
}
// Capabilities - what the current user can do with the file
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
}
/** A threaded reply attached to a comment. */
export interface GoogleDriveCommentReply {
id?: string
kind?: string
createdTime?: string
modifiedTime?: string
author?: GoogleDriveUser
htmlContent?: string
content?: string
deleted?: boolean
action?: string // 'resolve' | 'reopen'
}
/** A comment on a Google Drive file. */
export interface GoogleDriveComment {
id?: string
kind?: string
createdTime?: string
modifiedTime?: string
author?: GoogleDriveUser
htmlContent?: string
content?: string
deleted?: boolean
resolved?: boolean
anchor?: string
quotedFileContent?: {
mimeType?: string
value?: string
}
replies?: GoogleDriveCommentReply[]
}
// 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<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
hasAugmentedPermissions?: boolean
inheritedPermissionsDisabled?: boolean
downloadRestrictions?: {
restrictedForReaders?: boolean
}
// Revisions (fetched separately but included in response)
revisions?: GoogleDriveRevision[]
}
export interface GoogleDriveListResponse extends ToolResponse {
output: {
files: GoogleDriveFile[]
nextPageToken?: string
}
}
export interface GoogleDriveUploadResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export interface GoogleDriveGetContentResponse extends ToolResponse {
output: {
content: string
metadata: GoogleDriveFile
}
}
export interface GoogleDriveDownloadResponse extends ToolResponse {
output: {
file: {
name: string
mimeType: string
data: string
size: number
}
metadata: GoogleDriveFile
}
}
export interface GoogleDriveToolParams {
accessToken: string
folderId?: string
folderSelector?: string
fileId?: string
fileName?: string
file?: UserFile
content?: string
mimeType?: string
query?: string
pageSize?: number
pageToken?: string
exportMimeType?: string
includeRevisions?: boolean
}
export type GoogleDriveResponse =
| GoogleDriveUploadResponse
| GoogleDriveGetContentResponse
| GoogleDriveDownloadResponse
| GoogleDriveListResponse
+93
View File
@@ -0,0 +1,93 @@
import type { GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveUnshareParams extends GoogleDriveToolParams {
fileId: string
permissionId: string
}
interface GoogleDriveUnshareResponse extends ToolResponse {
output: {
removed: boolean
fileId: string
permissionId: string
}
}
export const unshareTool: ToolConfig<GoogleDriveUnshareParams, GoogleDriveUnshareResponse> = {
id: 'google_drive_unshare',
name: 'Unshare Google Drive File',
description: 'Remove a permission from a file (revoke access)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to modify permissions on',
},
permissionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the permission to remove (use list_permissions to find this)',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/permissions/${params.permissionId?.trim()}`
)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response, params) => {
if (!response.ok) {
const data = await response.json()
throw new Error(data.error?.message || 'Failed to remove permission from Google Drive file')
}
return {
success: true,
output: {
removed: true,
fileId: params?.fileId ?? '',
permissionId: params?.permissionId ?? '',
},
}
},
outputs: {
removed: {
type: 'boolean',
description: 'Whether the permission was successfully removed',
},
fileId: {
type: 'string',
description: 'The ID of the file',
},
permissionId: {
type: 'string',
description: 'The ID of the removed permission',
},
},
}
+88
View File
@@ -0,0 +1,88 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveUntrashParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveUntrashResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const untrashTool: ToolConfig<GoogleDriveUntrashParams, GoogleDriveUntrashResponse> = {
id: 'google_drive_untrash',
name: 'Restore Google Drive File',
description: 'Restore a file from the trash in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to restore from trash',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: () => ({
trashed: false,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to restore Google Drive file from trash')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The restored file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
trashed: { type: 'boolean', description: 'Whether file is in trash (should be false)' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
parents: { type: 'json', description: 'Parent folder IDs' },
},
},
},
}
+144
View File
@@ -0,0 +1,144 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveUpdateParams extends GoogleDriveToolParams {
fileId: string
name?: string
description?: string
addParents?: string
removeParents?: string
starred?: boolean
}
interface GoogleDriveUpdateResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const updateTool: ToolConfig<GoogleDriveUpdateParams, GoogleDriveUpdateResponse> = {
id: 'google_drive_update',
name: 'Update Google Drive File',
description: 'Update file metadata in Google Drive (rename, move, star, add description)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New name for the file',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New description for the file',
},
addParents: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of parent folder IDs to add (moves file to these folders)',
},
removeParents: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of parent folder IDs to remove',
},
starred: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to star or unstar the file',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
if (params.addParents) {
url.searchParams.append('addParents', params.addParents.trim())
}
if (params.removeParents) {
url.searchParams.append('removeParents', params.removeParents.trim())
}
return url.toString()
},
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.name !== undefined) {
body.name = params.name
}
if (params.description !== undefined) {
body.description = params.description
}
if (params.starred !== undefined) {
body.starred = params.starred
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json().catch(() => ({}) as any)
if (!response.ok) {
throw new Error(
data.error?.message ||
`Failed to update Google Drive file (${response.status} ${response.statusText})`
)
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The updated file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
description: { type: 'string', description: 'File description', optional: true },
starred: { type: 'boolean', description: 'Whether file is starred' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
parents: { type: 'json', description: 'Parent folder IDs' },
modifiedTime: { type: 'string', description: 'Last modification time' },
},
},
},
}
+336
View File
@@ -0,0 +1,336 @@
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,
} from '@/tools/google_drive/utils'
import type { ToolConfig } from '@/tools/types'
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 with complete metadata returned',
version: '1.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Drive API',
},
fileName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the file to upload',
},
file: {
type: 'file',
required: false,
visibility: 'user-only',
description: 'Binary file to upload (UserFile object)',
},
content: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Text content to upload (use this OR file, not both)',
},
mimeType: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The MIME type of the file to upload (auto-detected from file if not provided)',
},
folderSelector: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Google Drive folder ID to upload the file to (e.g., 1ABCxyz...)',
},
folderId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The ID of the folder to upload the file to (internal use)',
},
},
request: {
url: (params) => {
// Use custom API route if file is provided, otherwise use Google Drive API directly
if (params.file) {
return '/api/tools/google_drive/upload'
}
return 'https://www.googleapis.com/drive/v3/files?supportsAllDrives=true'
},
method: 'POST',
headers: (params) => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
}
// Google Drive API for text-only uploads needs Authorization
if (!params.file) {
headers.Authorization = `Bearer ${params.accessToken}`
}
return headers
},
body: (params) => {
// Custom route handles file uploads
if (params.file) {
return {
accessToken: params.accessToken,
fileName: params.fileName,
file: params.file,
mimeType: params.mimeType,
folderId: params.folderSelector || params.folderId,
}
}
// Original text-only upload logic
const metadata: {
name: string | undefined
mimeType: string
parents?: string[]
} = {
name: params.fileName, // Important: Always include the filename in metadata
mimeType: params.mimeType || 'text/plain',
}
// Add parent folder if specified (prefer folderSelector over folderId)
const parentFolderId = params.folderSelector || params.folderId
if (parentFolderId && parentFolderId.trim() !== '') {
metadata.parents = [parentFolderId]
}
return metadata
},
},
transformResponse: async (response: Response, params?: GoogleDriveToolParams) => {
try {
const data = await response.json()
// Handle custom API route response (for file uploads)
if (params?.file && data.success !== undefined) {
if (!data.success) {
logger.error('Failed to upload file via custom API route', {
error: data.error,
})
throw new Error(data.error || 'Failed to upload file to Google Drive')
}
return {
success: true,
output: {
file: data.output.file,
},
}
}
// Handle Google Drive API response (for text-only uploads)
if (!response.ok) {
logger.error('Failed to create file in Google Drive', {
status: response.status,
statusText: response.statusText,
data,
})
throw new Error(data.error?.message || 'Failed to create file in Google Drive')
}
const fileId = data.id
const requestedMimeType = params?.mimeType || 'text/plain'
const authHeader =
response.headers.get('Authorization') || `Bearer ${params?.accessToken || ''}`
let preparedContent: string | undefined =
typeof params?.content === 'string' ? (params?.content as string) : undefined
if (requestedMimeType === 'application/vnd.google-apps.spreadsheet' && params?.content) {
const { csv, rowCount, columnCount } = handleSheetsFormat(params.content as unknown)
if (csv !== undefined) {
preparedContent = csv
logger.info('Prepared CSV content for Google Sheets upload', {
fileId,
fileName: params?.fileName,
rowCount,
columnCount,
})
}
}
const uploadMimeType = GOOGLE_WORKSPACE_MIME_TYPES.includes(requestedMimeType)
? SOURCE_MIME_TYPES[requestedMimeType] || 'text/plain'
: requestedMimeType
logger.info('Uploading content to file', {
fileId,
fileName: params?.fileName,
requestedMimeType,
uploadMimeType,
})
const uploadResponse = await fetch(
`https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media&supportsAllDrives=true`,
{
method: 'PATCH',
headers: {
Authorization: authHeader,
'Content-Type': uploadMimeType,
},
body: preparedContent !== undefined ? preparedContent : params?.content || '',
}
)
if (!uploadResponse.ok) {
const uploadError = await uploadResponse.json()
logger.error('Failed to upload content to file', {
status: uploadResponse.status,
statusText: uploadResponse.statusText,
error: uploadError,
})
throw new Error(uploadError.error?.message || 'Failed to upload content to file')
}
if (GOOGLE_WORKSPACE_MIME_TYPES.includes(requestedMimeType)) {
logger.info('Updating file name to ensure it persists after conversion', {
fileId,
fileName: params?.fileName,
})
const updateNameResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}?supportsAllDrives=true`,
{
method: 'PATCH',
headers: {
Authorization: authHeader,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: params?.fileName,
}),
}
)
if (!updateNameResponse.ok) {
logger.warn('Failed to update filename after conversion, but content was uploaded', {
status: updateNameResponse.status,
statusText: updateNameResponse.statusText,
})
}
}
// Fetch complete file metadata with all fields
const finalFileResponse = await fetch(
`https://www.googleapis.com/drive/v3/files/${fileId}?supportsAllDrives=true&fields=${ALL_FILE_FIELDS}`,
{
headers: {
Authorization: authHeader,
},
}
)
const finalFile = await finalFileResponse.json()
return {
success: true,
output: {
file: finalFile,
},
}
} catch (error: any) {
logger.error('Error in upload transformation', {
error: error.message,
stack: error.stack,
})
throw error
}
},
outputs: {
file: {
type: 'object',
description: 'Complete uploaded file metadata from Google Drive',
properties: {
// Basic Info
id: { type: 'string', description: 'Google Drive file ID' },
kind: { type: 'string', description: 'Resource type identifier' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
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' },
},
},
},
}
+272
View File
@@ -0,0 +1,272 @@
// 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',
'labelInfo',
'hasAugmentedPermissions',
'inheritedPermissionsDisabled',
'downloadRestrictions',
].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(',')
/** All reply fields requested from the Google Drive API v3. */
const ALL_REPLY_FIELDS = [
'id',
'kind',
'createdTime',
'modifiedTime',
'author',
'htmlContent',
'content',
'deleted',
'action',
].join(',')
/** All comment fields requested from the Google Drive API v3. */
export const ALL_COMMENT_FIELDS = [
'id',
'kind',
'createdTime',
'modifiedTime',
'author',
'htmlContent',
'content',
'deleted',
'resolved',
'anchor',
'quotedFileContent',
`replies(${ALL_REPLY_FIELDS})`,
].join(',')
/**
* Maximum bytes accepted when exporting a Google Workspace file.
* Mirrors Google's own 10 MB export ceiling and keeps memory bounded.
*/
export const MAX_EXPORT_BYTES = 10 * 1024 * 1024
export const GOOGLE_WORKSPACE_MIME_TYPES = [
'application/vnd.google-apps.document', // Google Docs
'application/vnd.google-apps.spreadsheet', // Google Sheets
'application/vnd.google-apps.presentation', // Google Slides
'application/vnd.google-apps.drawing', // Google Drawings
'application/vnd.google-apps.form', // Google Forms
'application/vnd.google-apps.script', // Google Apps Scripts
]
export const DEFAULT_EXPORT_FORMATS: Record<string, string> = {
'application/vnd.google-apps.document': 'text/plain',
'application/vnd.google-apps.spreadsheet': 'text/csv',
'application/vnd.google-apps.presentation': 'text/plain',
'application/vnd.google-apps.drawing': 'image/png',
'application/vnd.google-apps.form': 'application/zip',
'application/vnd.google-apps.script': 'application/vnd.google-apps.script+json',
}
/**
* Valid export formats per Google Workspace file type.
* See: https://developers.google.com/drive/api/guides/ref-export-formats
*/
export const VALID_EXPORT_FORMATS: Record<string, string[]> = {
'application/vnd.google-apps.document': [
'text/plain',
'text/html',
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.oasis.opendocument.text',
'application/rtf',
'application/epub+zip',
'text/markdown',
],
'application/vnd.google-apps.spreadsheet': [
'text/csv',
'text/tab-separated-values',
'application/pdf',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.oasis.opendocument.spreadsheet',
'application/zip',
],
'application/vnd.google-apps.presentation': [
'text/plain',
'application/pdf',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.oasis.opendocument.presentation',
'image/jpeg',
'image/png',
'image/svg+xml',
],
'application/vnd.google-apps.drawing': [
'application/pdf',
'image/jpeg',
'image/png',
'image/svg+xml',
],
'application/vnd.google-apps.form': ['application/zip'],
'application/vnd.google-apps.script': ['application/vnd.google-apps.script+json'],
}
export const SOURCE_MIME_TYPES: Record<string, string> = {
'application/vnd.google-apps.document': 'text/plain',
'application/vnd.google-apps.spreadsheet': 'text/csv',
'application/vnd.google-apps.presentation': 'application/vnd.ms-powerpoint',
}
export function handleSheetsFormat(input: unknown): {
csv?: string
rowCount: number
columnCount: number
} {
let workingValue: unknown = input
if (typeof workingValue === 'string') {
try {
workingValue = JSON.parse(workingValue)
} catch (_error) {
const csvString = workingValue as string
return { csv: csvString, rowCount: 0, columnCount: 0 }
}
}
if (!Array.isArray(workingValue)) {
return { rowCount: 0, columnCount: 0 }
}
let table: unknown[] = workingValue
if (
table.length > 0 &&
typeof (table as any)[0] === 'object' &&
(table as any)[0] !== null &&
!Array.isArray((table as any)[0])
) {
const allKeys = new Set<string>()
;(table as any[]).forEach((obj) => {
if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => allKeys.add(key))
}
})
const headers = Array.from(allKeys)
const rows = (table as any[]).map((obj) => {
if (!obj || typeof obj !== 'object') {
return Array(headers.length).fill('')
}
return headers.map((key) => {
const value = (obj as Record<string, unknown>)[key]
if (value !== null && typeof value === 'object') {
return JSON.stringify(value)
}
return value === undefined ? '' : (value as any)
})
})
table = [headers, ...rows]
}
const escapeCell = (cell: unknown): string => {
if (cell === null || cell === undefined) return ''
const stringValue = String(cell)
const mustQuote = /[",\n\r]/.test(stringValue)
const doubledQuotes = stringValue.replace(/"/g, '""')
return mustQuote ? `"${doubledQuotes}"` : doubledQuotes
}
const rowsAsStrings = (table as unknown[]).map((row) => {
if (!Array.isArray(row)) {
return escapeCell(row)
}
return row.map((cell) => escapeCell(cell)).join(',')
})
const csv = rowsAsStrings.join('\r\n')
const rowCount = Array.isArray(table) ? (table as any[]).length : 0
const columnCount =
Array.isArray(table) && Array.isArray((table as any[])[0]) ? (table as any[])[0].length : 0
return { csv, rowCount, columnCount }
}