mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
feat(google-drive): added additional tools to interact with google drive (#387)
* added additional google drive tools * added folder id and doc id fields for google docs and google drive, added additional google drive tools * added google drive upload to list of google drive tools * consolidated consts * resolved PR comments
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Google Drive
|
||||
description: Upload, download, and list files
|
||||
description: Create and list files
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from '@/components/ui/block-info-card'
|
||||
@@ -72,7 +72,7 @@ In Sim Studio, the Google Drive integration enables your agents to interact dire
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Google Drive functionality to manage files and folders. Upload new files, download existing ones, and list contents of folders using OAuth authentication. Supports file operations with custom MIME types and folder organization.
|
||||
Integrate Google Drive functionality to manage files and folders. Upload new files, get content from existing files, create new folders, and list contents of folders using OAuth authentication. Supports file operations with custom MIME types and folder organization.
|
||||
|
||||
## Tools
|
||||
|
||||
@@ -104,22 +104,23 @@ Upload a file to Google Drive
|
||||
| `modifiedTime` | string |
|
||||
| `parents` | string |
|
||||
|
||||
### `google_drive_download`
|
||||
### `google_drive_create_folder`
|
||||
|
||||
Download a file from Google Drive
|
||||
Create a new folder in Google Drive
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| ------------- | ------ | -------- | ----------------------------------------- |
|
||||
| `accessToken` | string | Yes | The access token for the Google Drive API |
|
||||
| `fileId` | string | Yes | The ID of the file to download |
|
||||
| Parameter | Type | Required | Description |
|
||||
| ------------- | ------ | -------- | ------------------------------------------------------- |
|
||||
| `accessToken` | string | Yes | The access token for the Google Drive API |
|
||||
| `fileName` | string | Yes | Name of the folder to create |
|
||||
| `folderId` | string | No | ID of the parent folder \(leave empty for root folder\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type |
|
||||
| ---------------- | ------ |
|
||||
| `metadata` | string |
|
||||
| `file` | string |
|
||||
| `name` | string |
|
||||
| `mimeType` | string |
|
||||
| `webViewLink` | string |
|
||||
@@ -159,15 +160,19 @@ List files and folders in Google Drive
|
||||
|
||||
## Block Configuration
|
||||
|
||||
No configuration parameters required.
|
||||
### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| ----------- | ------ | -------- | ----------- |
|
||||
| `operation` | string | Yes | Operation |
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Type | Description |
|
||||
| ------------ | ------ | ------------------------ |
|
||||
| `response` | object | Output from response |
|
||||
| ↳ `content` | string | content of the response |
|
||||
| ↳ `metadata` | json | metadata of the response |
|
||||
| Output | Type | Description |
|
||||
| ---------- | ------ | --------------------- |
|
||||
| `response` | object | Output from response |
|
||||
| ↳ `file` | json | file of the response |
|
||||
| ↳ `files` | json | files of the response |
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -9,16 +9,27 @@ import { BlockInfoCard } from '@/components/ui/block-info-card'
|
||||
type="thinking"
|
||||
color="#181C1E"
|
||||
icon={true}
|
||||
iconSvg={`<svg className="block-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="currentColor">
|
||||
<title>Brain</title>
|
||||
<g id="Brain">
|
||||
<path d="M11,5.34c-.12,1,.26.66-1,.66A7,7,0,0,0,4.85,17.75,7,7,0,0,0,2.62,28.48,12.14,12.14,0,0,0,.06,37.22C1,46.59,12.33,51.56,19.31,45A11.66,11.66,0,0,0,23,36.51V6.22C23-1.57,11.86-2.14,11,5.34Zm-.55,40.55a8.89,8.89,0,0,1-4.78-2.18l.42-.42a2.1,2.1,0,0,1,2.42-.4,1,1,0,0,0,.9-1.78c-2-1-1.24.83-2-3.11-.61-3,1.55-4.14,0-4.89-1.79-.9-2.38,3.32-2,5.28.79,4,.85,1.7-1.19,3.94a10,10,0,0,1-.13-12.5c1.51,1,4.86,2,4.86.17a1,1,0,0,0-1-1c-5.79,0-6.94-8.33-1.27-9.82C8,19.87,11,20.73,11,19a1,1,0,0,0-1-1,5,5,0,1,1,1.44-9.77C13,12,18,13,18,11a1,1,0,0,0-1-1,4,4,0,1,1,4-3.78v9.37l-1.16,1.15a3.42,3.42,0,0,1-4.29.43,1,1,0,0,0-1.38.28c-1.12,1.68,3.7,3.68,6.83.92V36.51A9.3,9.3,0,0,1,10.49,45.89Z" />
|
||||
<path d="M16.21,23.79a3.14,3.14,0,0,0-4.42,0c-1,1-2-.42-3.08-1.5a1,1,0,0,0-1.42,1.42l.86.85-1.47.49A1,1,0,0,0,7.32,27L10.18,26c2.71.74,3.26-2.15,4.61-.79l2.5,2.5a1,1,0,0,0,1.42-1.42Z" />
|
||||
<path d="M17,33H16a3,3,0,0,0-3,3,1,1,0,0,1-1,1H11a1,1,0,0,0,0,2h1a3,3,0,0,0,3-3,1,1,0,0,1,1-1h1A1,1,0,0,0,17,33Z" />
|
||||
<path d="M45.36,28.49a7,7,0,0,0-2.21-10.74A7,7,0,0,0,38,6c-1.28,0-.93.35-1-.63A6,6,0,0,0,31,0a6.13,6.13,0,0,0-6,6.22V36.51C25,42.89,30.26,48.4,36.82,48A12,12,0,0,0,45.36,28.49Zm-1.65,13.8A4.92,4.92,0,0,0,42,41c.55-2.79,1.21-4.79-.13-7.47a1,1,0,0,0-1.78.9c1,2.06.45,3.65-.07,6.25-3.33.28-1.92,2.85-.59,2.19s2.33.34,2.88.84A9.28,9.28,0,0,1,27,36.51V18.37c3.12,2.75,8,.76,6.83-.92a1,1,0,0,0-1.38-.28,3.42,3.42,0,0,1-4.29-.43L27,15.59V6.22A4,4,0,1,1,31,10a1,1,0,0,0-1,1c0,2.05,5.07,1,6.57-2.78A5,5,0,1,1,38,18a1,1,0,0,0,0,2,7,7,0,0,0,3.27-.82C47,20.68,45.73,29,40,29a1,1,0,0,0,0,2,6.89,6.89,0,0,0,3.86-1.17C48.62,35.85,44,42.55,43.71,42.29Z" />
|
||||
<path d="M41,27a1,1,0,0,0,.32-1.95l-1.47-.49.86-.85a1,1,0,0,0-1.42-1.42c-1.09,1.09-2.07,2.51-3.08,1.5a3.14,3.14,0,0,0-4.42,0l-2.5,2.5A1,1,0,0,0,30,28c.56,0,.54-.13,3.21-2.79,1.38-1.38,1.86,1.54,4.61.79C40.92,27,40.78,27,41,27Z" />
|
||||
<path d="M37,37H36a1,1,0,0,1-1-1,3,3,0,0,0-3-3H31a1,1,0,0,0,0,2h1a1,1,0,0,1,1,1,3,3,0,0,0,3,3h1A1,1,0,0,0,37,37Z" />
|
||||
</g>
|
||||
iconSvg={`<svg className="block-icon"
|
||||
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" />
|
||||
<path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z" />
|
||||
<path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4" />
|
||||
<path d="M17.599 6.5a3 3 0 0 0 .399-1.375" />
|
||||
<path d="M6.003 5.125A3 3 0 0 0 6.401 6.5" />
|
||||
<path d="M3.477 10.896a4 4 0 0 1 .585-.396" />
|
||||
<path d="M19.938 10.5a4 4 0 0 1 .585.396" />
|
||||
<path d="M6 18a4 4 0 0 1-1.967-.516" />
|
||||
<path d="M19.967 17.484A4 4 0 0 1 18 18" />
|
||||
</svg>`}
|
||||
/>
|
||||
|
||||
|
||||
@@ -55,11 +55,13 @@ export const contentTypeMap: Record<string, string> = {
|
||||
ts: 'application/typescript',
|
||||
// Document formats
|
||||
pdf: 'application/pdf',
|
||||
googleDoc: 'application/vnd.google-apps.document',
|
||||
doc: 'application/msword',
|
||||
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
// Spreadsheet formats
|
||||
xls: 'application/vnd.ms-excel',
|
||||
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
googleSheet: 'application/vnd.google-apps.spreadsheet',
|
||||
// Presentation formats
|
||||
ppt: 'application/vnd.ms-powerpoint',
|
||||
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
@@ -71,6 +73,8 @@ export const contentTypeMap: Record<string, string> = {
|
||||
svg: 'image/svg+xml',
|
||||
// Archive formats
|
||||
zip: 'application/zip',
|
||||
// Folder format
|
||||
googleFolder: 'application/vnd.google-apps.folder',
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -77,12 +77,8 @@ export const GoogleDocsBlock: BlockConfig<GoogleDocsResponse> = {
|
||||
title: 'Or Enter Document ID Manually',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'ID of the document (from URL)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'read',
|
||||
and: { field: 'documentId', value: '' },
|
||||
},
|
||||
placeholder: 'ID of the document',
|
||||
condition: { field: 'operation', value: 'read' },
|
||||
},
|
||||
// Manual Document ID for write operation
|
||||
{
|
||||
@@ -90,12 +86,8 @@ export const GoogleDocsBlock: BlockConfig<GoogleDocsResponse> = {
|
||||
title: 'Or Enter Document ID Manually',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'ID of the document (from URL)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'write',
|
||||
and: { field: 'documentId', value: '' },
|
||||
},
|
||||
placeholder: 'ID of the document',
|
||||
condition: { field: 'operation', value: 'write' },
|
||||
},
|
||||
// Create-specific Fields
|
||||
{
|
||||
@@ -106,9 +98,23 @@ export const GoogleDocsBlock: BlockConfig<GoogleDocsResponse> = {
|
||||
placeholder: 'Enter title for the new document',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
},
|
||||
// Folder Selector for create operation
|
||||
{
|
||||
id: 'folderSelector',
|
||||
title: 'Select Parent Folder',
|
||||
type: 'file-selector',
|
||||
layout: 'full',
|
||||
provider: 'google-drive',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [],
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
placeholder: 'Select a parent folder',
|
||||
condition: { field: 'operation', value: 'create' },
|
||||
},
|
||||
// Manual Folder ID for create operation
|
||||
{
|
||||
id: 'folderId',
|
||||
title: 'Parent Folder ID (Optional)',
|
||||
title: 'Or Enter Parent Folder ID Manually',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'ID of the parent folder (leave empty for root folder)',
|
||||
@@ -149,22 +155,16 @@ export const GoogleDocsBlock: BlockConfig<GoogleDocsResponse> = {
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { credential, documentId, manualDocumentId, ...rest } = params
|
||||
const { credential, documentId, manualDocumentId, folderSelector, folderId, ...rest } =
|
||||
params
|
||||
|
||||
// Use the selected document ID or the manually entered one
|
||||
// If documentId is provided, it's from the file selector and contains the file ID
|
||||
// If not, fall back to manually entered ID
|
||||
const effectiveDocumentId = (documentId || manualDocumentId || '').trim()
|
||||
|
||||
if (params.operation !== 'create' && !effectiveDocumentId) {
|
||||
throw new Error(
|
||||
'Document ID is required. Please select a document or enter an ID manually.'
|
||||
)
|
||||
}
|
||||
const effectiveFolderId = (folderSelector || folderId || '').trim()
|
||||
|
||||
return {
|
||||
...rest,
|
||||
documentId: effectiveDocumentId,
|
||||
folderId: effectiveFolderId,
|
||||
credential,
|
||||
}
|
||||
},
|
||||
@@ -176,6 +176,7 @@ export const GoogleDocsBlock: BlockConfig<GoogleDocsResponse> = {
|
||||
documentId: { type: 'string', required: false },
|
||||
manualDocumentId: { type: 'string', required: false },
|
||||
title: { type: 'string', required: false },
|
||||
folderSelector: { type: 'string', required: false },
|
||||
folderId: { type: 'string', required: false },
|
||||
content: { type: 'string', required: false },
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { GoogleDriveIcon } from '@/components/icons'
|
||||
import {
|
||||
GoogleDriveDownloadResponse,
|
||||
GoogleDriveGetContentResponse,
|
||||
GoogleDriveListResponse,
|
||||
GoogleDriveUploadResponse,
|
||||
} from '@/tools/google_drive/types'
|
||||
@@ -8,32 +8,33 @@ import { BlockConfig } from '../types'
|
||||
|
||||
type GoogleDriveResponse =
|
||||
| GoogleDriveUploadResponse
|
||||
| GoogleDriveDownloadResponse
|
||||
| GoogleDriveGetContentResponse
|
||||
| GoogleDriveListResponse
|
||||
|
||||
export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
|
||||
type: 'google_drive',
|
||||
name: 'Google Drive',
|
||||
description: 'Upload, download, and list files',
|
||||
description: 'Create, upload, and list files',
|
||||
longDescription:
|
||||
'Integrate Google Drive functionality to manage files and folders. Upload new files, download existing ones, and list contents of folders using OAuth authentication. Supports file operations with custom MIME types and folder organization.',
|
||||
'Integrate Google Drive functionality to manage files and folders. Upload new files, get content from existing files, create new folders, and list contents of folders using OAuth authentication. Supports file operations with custom MIME types and folder organization.',
|
||||
docsLink: 'https://docs.simstudio.ai/tools/google_drive',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
icon: GoogleDriveIcon,
|
||||
subBlocks: [
|
||||
// Operation selector
|
||||
// {
|
||||
// id: 'operation',
|
||||
// title: 'Operation',
|
||||
// type: 'dropdown',
|
||||
// layout: 'full',
|
||||
// options: [
|
||||
// // { label: 'Upload File', id: 'upload' },
|
||||
// // { label: 'Download File', id: 'download' },
|
||||
// { label: 'List Files', id: 'list' },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'Create Folder', id: 'create_folder' },
|
||||
{ label: 'Upload File', id: 'upload' },
|
||||
// { label: 'Get File Content', id: 'get_content' },
|
||||
{ label: 'List Files', id: 'list' },
|
||||
],
|
||||
},
|
||||
// Google Drive Credentials
|
||||
{
|
||||
id: 'credential',
|
||||
@@ -51,7 +52,7 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
|
||||
title: 'File Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Name for the uploaded file (e.g., document.txt)',
|
||||
placeholder: 'Name of the file',
|
||||
condition: { field: 'operation', value: 'upload' },
|
||||
},
|
||||
{
|
||||
@@ -65,41 +66,126 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
|
||||
{
|
||||
id: 'mimeType',
|
||||
title: 'MIME Type',
|
||||
type: 'short-input',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
placeholder:
|
||||
'File MIME type (default: text/plain, e.g., text/plain, application/json, text/csv)',
|
||||
options: [
|
||||
{ label: 'Google Doc', id: 'application/vnd.google-apps.document' },
|
||||
{ label: 'Google Sheet', id: 'application/vnd.google-apps.spreadsheet' },
|
||||
{ label: 'Google Slides', id: 'application/vnd.google-apps.presentation' },
|
||||
{ label: 'PDF (application/pdf)', id: 'application/pdf' },
|
||||
],
|
||||
placeholder: 'Select a file type',
|
||||
condition: { field: 'operation', value: 'upload' },
|
||||
},
|
||||
{
|
||||
id: 'folderSelector',
|
||||
title: 'Select Parent Folder',
|
||||
type: 'file-selector',
|
||||
layout: 'full',
|
||||
provider: 'google-drive',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: ['https://www.googleapis.com/auth/drive.file'],
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
placeholder: 'Select a parent folder',
|
||||
condition: { field: 'operation', value: 'upload' },
|
||||
},
|
||||
{
|
||||
id: 'folderId',
|
||||
title: 'Parent Folder ID',
|
||||
title: 'Or Enter Parent Folder ID Manually',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'ID of the parent folder (leave empty for root folder)',
|
||||
condition: { field: 'operation', value: 'upload' },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'upload',
|
||||
},
|
||||
},
|
||||
// Download Fields
|
||||
// Get Content Fields
|
||||
// {
|
||||
// id: 'fileId',
|
||||
// title: 'Select File',
|
||||
// type: 'file-selector',
|
||||
// layout: 'full',
|
||||
// provider: 'google-drive',
|
||||
// serviceId: 'google-drive',
|
||||
// requiredScopes: [],
|
||||
// placeholder: 'Select a file',
|
||||
// condition: { field: 'operation', value: 'get_content' },
|
||||
// },
|
||||
// // Manual File ID input (shown only when no file is selected)
|
||||
// {
|
||||
// id: 'fileId',
|
||||
// title: 'Or Enter File ID Manually',
|
||||
// type: 'short-input',
|
||||
// layout: 'full',
|
||||
// placeholder: 'ID of the file to get content from',
|
||||
// condition: {
|
||||
// field: 'operation',
|
||||
// value: 'get_content',
|
||||
// and: {
|
||||
// field: 'fileId',
|
||||
// value: '',
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// Export format for Google Workspace files
|
||||
// {
|
||||
// id: 'mimeType',
|
||||
// title: 'Export Format',
|
||||
// type: 'dropdown',
|
||||
// layout: 'full',
|
||||
// options: [
|
||||
// { label: 'Plain Text', id: 'text/plain' },
|
||||
// { label: 'HTML', id: 'text/html' },
|
||||
// ],
|
||||
// placeholder: 'Optional: Choose export format for Google Workspace files',
|
||||
// condition: { field: 'operation', value: 'get_content' },
|
||||
// },
|
||||
// Create Folder Fields
|
||||
{
|
||||
id: 'fileId',
|
||||
title: 'File ID',
|
||||
id: 'fileName',
|
||||
title: 'Folder Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'ID of the file to download (find in file URL or by listing files)',
|
||||
condition: { field: 'operation', value: 'download' },
|
||||
placeholder: 'Name for the new folder',
|
||||
condition: { field: 'operation', value: 'create_folder' },
|
||||
},
|
||||
{
|
||||
id: 'folderSelector',
|
||||
title: 'Select Parent Folder',
|
||||
type: 'file-selector',
|
||||
layout: 'full',
|
||||
provider: 'google-drive',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: ['https://www.googleapis.com/auth/drive.file'],
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
placeholder: 'Select a parent folder',
|
||||
condition: { field: 'operation', value: 'create_folder' },
|
||||
},
|
||||
// Manual Folder ID input (shown only when no folder is selected)
|
||||
{
|
||||
id: 'folderId',
|
||||
title: 'Or Enter Parent Folder ID Manually',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'ID of the parent folder (leave empty for root folder)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_folder',
|
||||
},
|
||||
},
|
||||
// List Fields - Folder Selector
|
||||
{
|
||||
id: 'folderId',
|
||||
id: 'folderSelector',
|
||||
title: 'Select Folder',
|
||||
type: 'file-selector',
|
||||
layout: 'full',
|
||||
provider: 'google-drive',
|
||||
serviceId: 'google-drive',
|
||||
requiredScopes: [],
|
||||
requiredScopes: ['https://www.googleapis.com/auth/drive.file'],
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
placeholder: 'Select a folder',
|
||||
// condition: { field: 'operation', value: 'list' },
|
||||
placeholder: 'Select a folder to list files from',
|
||||
condition: { field: 'operation', value: 'list' },
|
||||
},
|
||||
// Manual Folder ID input (shown only when no folder is selected)
|
||||
{
|
||||
@@ -109,12 +195,8 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
|
||||
layout: 'full',
|
||||
placeholder: 'ID of the folder to list (leave empty for root folder)',
|
||||
condition: {
|
||||
// field: 'operation',
|
||||
// value: 'list',
|
||||
// and: {
|
||||
field: 'folderId',
|
||||
value: '',
|
||||
// },
|
||||
field: 'operation',
|
||||
value: 'list',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -123,7 +205,7 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Search for specific files (e.g., name contains "report")',
|
||||
// condition: { field: 'operation', value: 'list' },
|
||||
condition: { field: 'operation', value: 'list' },
|
||||
},
|
||||
{
|
||||
id: 'pageSize',
|
||||
@@ -131,58 +213,62 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Number of results (default: 100, max: 1000)',
|
||||
// condition: { field: 'operation', value: 'list' },
|
||||
condition: { field: 'operation', value: 'list' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: ['google_drive_upload', 'google_drive_download', 'google_drive_list'],
|
||||
access: ['google_drive_upload', 'google_drive_create_folder', 'google_drive_list'],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
// Since we only have 'list' now, we can simplify this
|
||||
return 'google_drive_list'
|
||||
|
||||
// switch (params.operation) {
|
||||
// case 'upload':
|
||||
// return 'google_drive_upload'
|
||||
// case 'download':
|
||||
// return 'google_drive_download'
|
||||
// case 'list':
|
||||
// return 'google_drive_list'
|
||||
// default:
|
||||
// throw new Error(`Invalid Google Drive operation: ${params.operation}`)
|
||||
// }
|
||||
switch (params.operation) {
|
||||
case 'upload':
|
||||
return 'google_drive_upload'
|
||||
// case 'get_content':
|
||||
// return 'google_drive_get_content'
|
||||
case 'create_folder':
|
||||
return 'google_drive_create_folder'
|
||||
case 'list':
|
||||
return 'google_drive_list'
|
||||
default:
|
||||
throw new Error(`Invalid Google Drive operation: ${params.operation}`)
|
||||
}
|
||||
},
|
||||
params: (params) => {
|
||||
const { credential, folderId, ...rest } = params
|
||||
const { credential, folderId, folderSelector, mimeType, ...rest } = params
|
||||
|
||||
// Use folderSelector if provided, otherwise use folderId
|
||||
const effectiveFolderId = folderSelector || folderId || ''
|
||||
|
||||
return {
|
||||
accessToken: credential,
|
||||
folderId: folderId?.trim() || '',
|
||||
folderId: effectiveFolderId.trim(),
|
||||
pageSize: rest.pageSize ? parseInt(rest.pageSize as string, 10) : undefined,
|
||||
mimeType: mimeType,
|
||||
...rest,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
// operation: { type: 'string', required: true },
|
||||
operation: { type: 'string', required: true },
|
||||
credential: { type: 'string', required: true },
|
||||
// Upload operation inputs
|
||||
// Upload and Create Folder operation inputs
|
||||
fileName: { type: 'string', required: false },
|
||||
content: { type: 'string', required: false },
|
||||
mimeType: { type: 'string', required: false },
|
||||
// Download operation inputs
|
||||
fileId: { type: 'string', required: false },
|
||||
// Get Content operation inputs
|
||||
// fileId: { type: 'string', required: false },
|
||||
// List operation inputs
|
||||
folderId: { type: 'string', required: false },
|
||||
folderSelector: { type: 'string', required: false },
|
||||
query: { type: 'string', required: false },
|
||||
pageSize: { type: 'number', required: false },
|
||||
},
|
||||
outputs: {
|
||||
response: {
|
||||
type: {
|
||||
content: 'string',
|
||||
metadata: 'json',
|
||||
file: 'json',
|
||||
files: 'json',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -107,7 +107,7 @@ export interface SubBlockConfig {
|
||||
not?: boolean
|
||||
and?: {
|
||||
field: string
|
||||
value: string | number | boolean | Array<string | number | boolean>
|
||||
value: string | number | boolean | Array<string | number | boolean> | undefined
|
||||
not?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export const createTool: ToolConfig<GoogleDocsToolParams, GoogleDocsCreateRespon
|
||||
},
|
||||
request: {
|
||||
url: () => {
|
||||
return 'https://docs.googleapis.com/v1/documents'
|
||||
return 'https://www.googleapis.com/drive/v3/files'
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
@@ -49,51 +49,52 @@ export const createTool: ToolConfig<GoogleDocsToolParams, GoogleDocsCreateRespon
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
// Validate title
|
||||
if (!params.title) {
|
||||
throw new Error('Title is required')
|
||||
}
|
||||
|
||||
// Create a new document with the specified title
|
||||
const requestBody = {
|
||||
title: params.title,
|
||||
const requestBody: any = {
|
||||
name: params.title,
|
||||
mimeType: 'application/vnd.google-apps.document',
|
||||
}
|
||||
|
||||
// Add parent folder if specified
|
||||
if (params.folderId || params.folderSelector) {
|
||||
requestBody.parents = [params.folderId || params.folderSelector]
|
||||
}
|
||||
|
||||
return requestBody
|
||||
},
|
||||
},
|
||||
postProcess: async (result, params, executeTool) => {
|
||||
// Only add content if it was provided and not already added during creation
|
||||
// The Google Docs API doesn't directly support content in the create request,
|
||||
// so we need to add it separately via the write tool
|
||||
if (result.success && params.content) {
|
||||
const documentId = result.output.metadata.documentId
|
||||
if (!result.success) {
|
||||
return result
|
||||
}
|
||||
|
||||
if (documentId) {
|
||||
try {
|
||||
const writeParams = {
|
||||
accessToken: params.accessToken,
|
||||
documentId: documentId,
|
||||
content: params.content,
|
||||
}
|
||||
const documentId = result.output.metadata.documentId
|
||||
|
||||
// Use the write tool to add content
|
||||
const writeResult = await executeTool('google_docs_write', writeParams)
|
||||
|
||||
if (!writeResult.success) {
|
||||
logger.warn(
|
||||
'Failed to add content to document, but document was created:',
|
||||
writeResult.error
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('Error adding content to document:', { error })
|
||||
// Don't fail the overall operation if adding content fails
|
||||
if (params.content && documentId) {
|
||||
try {
|
||||
const writeParams = {
|
||||
accessToken: params.accessToken,
|
||||
documentId: documentId,
|
||||
content: params.content,
|
||||
}
|
||||
|
||||
const writeResult = await executeTool('google_docs_write', writeParams)
|
||||
|
||||
if (!writeResult.success) {
|
||||
logger.warn(
|
||||
'Failed to add content to document, but document was created:',
|
||||
writeResult.error
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('Error adding content to document:', { error })
|
||||
// Don't fail the overall operation if adding content fails
|
||||
}
|
||||
}
|
||||
|
||||
// Return the original result regardless of post-processing outcome
|
||||
return result
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
@@ -113,13 +114,11 @@ export const createTool: ToolConfig<GoogleDocsToolParams, GoogleDocsCreateRespon
|
||||
try {
|
||||
// Get the response data
|
||||
const responseText = await response.text()
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
const documentId = data.documentId
|
||||
const title = data.title
|
||||
const documentId = data.id
|
||||
const title = data.name
|
||||
|
||||
// Create document metadata
|
||||
const metadata = {
|
||||
documentId,
|
||||
title: title || 'Untitled Document',
|
||||
|
||||
@@ -22,7 +22,7 @@ export const readTool: ToolConfig<GoogleDocsToolParams, GoogleDocsReadResponse>
|
||||
request: {
|
||||
url: (params) => {
|
||||
// Ensure documentId is valid
|
||||
const documentId = params.documentId?.trim()
|
||||
const documentId = params.documentId?.trim() || params.manualDocumentId?.trim()
|
||||
if (!documentId) {
|
||||
throw new Error('Document ID is required')
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ export interface GoogleDocsCreateResponse extends ToolResponse {
|
||||
export interface GoogleDocsToolParams {
|
||||
accessToken: string
|
||||
documentId?: string
|
||||
manualDocumentId?: string
|
||||
title?: string
|
||||
content?: string
|
||||
folderId?: string
|
||||
folderSelector?: string
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const writeTool: ToolConfig<GoogleDocsToolParams, GoogleDocsWriteResponse
|
||||
request: {
|
||||
url: (params) => {
|
||||
// Ensure documentId is valid
|
||||
const documentId = params.documentId?.trim()
|
||||
const documentId = params.documentId?.trim() || params.manualDocumentId?.trim()
|
||||
if (!documentId) {
|
||||
throw new Error('Document ID is required')
|
||||
}
|
||||
|
||||
79
apps/sim/tools/google_drive/create_folder.ts
Normal file
79
apps/sim/tools/google_drive/create_folder.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { ToolConfig } from '../types'
|
||||
import { GoogleDriveToolParams, GoogleDriveUploadResponse } from './types'
|
||||
|
||||
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',
|
||||
version: '1.0',
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/drive.file'],
|
||||
},
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The access token for the Google Drive API',
|
||||
},
|
||||
fileName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Name of the folder to create',
|
||||
},
|
||||
folderId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'ID of the parent folder (leave empty for root folder)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: 'https://www.googleapis.com/drive/v3/files',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const metadata = {
|
||||
name: params.fileName,
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
...(params.folderId ? { parents: [params.folderId] } : {}),
|
||||
}
|
||||
|
||||
if (params.folderSelector) {
|
||||
metadata.parents = [params.folderSelector]
|
||||
}
|
||||
|
||||
return metadata
|
||||
},
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => ({}))
|
||||
throw new Error(data.error?.message || 'Failed to create folder in Google Drive')
|
||||
}
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
file: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
mimeType: data.mimeType,
|
||||
webViewLink: data.webViewLink,
|
||||
webContentLink: data.webContentLink,
|
||||
size: data.size,
|
||||
createdTime: data.createdTime,
|
||||
modifiedTime: data.modifiedTime,
|
||||
parents: data.parents,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
transformError: (error) => {
|
||||
return error.message || 'An error occurred while creating folder in Google Drive'
|
||||
},
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { ToolConfig } from '../types'
|
||||
import { GoogleDriveDownloadResponse, GoogleDriveToolParams } from './types'
|
||||
|
||||
export const downloadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveDownloadResponse> = {
|
||||
id: 'google_drive_download',
|
||||
name: 'Download from Google Drive',
|
||||
description: 'Download a file from Google Drive',
|
||||
version: '1.0',
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/drive.file'],
|
||||
},
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The access token for the Google Drive API',
|
||||
},
|
||||
fileId: { type: 'string', required: true, description: 'The ID of the file to download' },
|
||||
},
|
||||
request: {
|
||||
url: (params) => `https://www.googleapis.com/drive/v3/files/${params.fileId}?alt=media`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
throw new Error(error.error?.message || 'Failed to download file from Google Drive')
|
||||
}
|
||||
|
||||
// Get file metadata
|
||||
const metadataResponse = await fetch(
|
||||
`https://www.googleapis.com/drive/v3/files/${response.url.split('files/')[1].split('?')[0]}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: response.headers.get('Authorization') || '',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const metadata = await metadataResponse.json()
|
||||
const content = await response.text()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: metadata.id,
|
||||
name: metadata.name,
|
||||
mimeType: metadata.mimeType,
|
||||
webViewLink: metadata.webViewLink,
|
||||
webContentLink: metadata.webContentLink,
|
||||
size: metadata.size,
|
||||
createdTime: metadata.createdTime,
|
||||
modifiedTime: metadata.modifiedTime,
|
||||
parents: metadata.parents,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
transformError: (error) => {
|
||||
return error.message || 'An error occurred while downloading from Google Drive'
|
||||
},
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { ToolConfig } from '../types'
|
||||
import { GoogleDriveDownloadResponse } from './types'
|
||||
import { GoogleDriveToolParams } from './types'
|
||||
|
||||
const logger = createLogger('GoogleDriveExportTool')
|
||||
|
||||
export const exportTool: ToolConfig<
|
||||
GoogleDriveToolParams & { mimeType?: string },
|
||||
GoogleDriveDownloadResponse
|
||||
> = {
|
||||
id: 'google_drive_export',
|
||||
name: 'Export from Google Drive',
|
||||
description: 'Export a Google Workspace file (Docs, Sheets, Slides) from Google Drive',
|
||||
version: '1.0',
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/drive.file'],
|
||||
},
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The access token for the Google Drive API',
|
||||
},
|
||||
fileId: { type: 'string', required: true, description: 'The ID of the file to export' },
|
||||
mimeType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'The MIME type to export the file as',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) => {
|
||||
const exportMimeType = params.mimeType || 'application/pdf'
|
||||
return `https://www.googleapis.com/drive/v3/files/${params.fileId}/export?mimeType=${encodeURIComponent(exportMimeType)}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json()
|
||||
logger.error('Google Drive export error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error,
|
||||
fileId: response.url.split('files/')[1]?.split('?')[0],
|
||||
})
|
||||
throw new Error(
|
||||
`Failed to export file from Google Drive: ${response.status} ${response.statusText} - ${error.error?.message || 'Unknown error'}`
|
||||
)
|
||||
}
|
||||
|
||||
// Get file metadata
|
||||
const fileId = response.url.split('files/')[1]?.split('?')[0]
|
||||
const metadataResponse = await fetch(`https://www.googleapis.com/drive/v3/files/${fileId}`, {
|
||||
headers: {
|
||||
Authorization: response.headers.get('Authorization') || '',
|
||||
},
|
||||
})
|
||||
|
||||
if (!metadataResponse.ok) {
|
||||
const metadataError = await metadataResponse.json()
|
||||
logger.error('Google Drive metadata error:', {
|
||||
status: metadataResponse.status,
|
||||
statusText: metadataResponse.statusText,
|
||||
error: metadataError,
|
||||
})
|
||||
throw new Error(
|
||||
`Failed to get file metadata: ${metadataResponse.status} ${metadataResponse.statusText} - ${metadataError.error?.message || 'Unknown error'}`
|
||||
)
|
||||
}
|
||||
|
||||
const metadata = await metadataResponse.json()
|
||||
let content
|
||||
try {
|
||||
content = await response.text()
|
||||
} catch (error: any) {
|
||||
logger.error('Error reading response content:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
error: JSON.stringify(error),
|
||||
})
|
||||
throw new Error(`Failed to read file content: ${error?.message || 'Unknown error'}`)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: metadata.id,
|
||||
name: metadata.name,
|
||||
mimeType: metadata.mimeType,
|
||||
webViewLink: metadata.webViewLink,
|
||||
webContentLink: metadata.webContentLink,
|
||||
size: metadata.size,
|
||||
createdTime: metadata.createdTime,
|
||||
modifiedTime: metadata.modifiedTime,
|
||||
parents: metadata.parents,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
transformError: (error: any) => {
|
||||
logger.error('Export tool error:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
error: JSON.stringify(error, null, 2),
|
||||
})
|
||||
if (typeof error === 'string') {
|
||||
return error
|
||||
}
|
||||
return (
|
||||
error.message ||
|
||||
JSON.stringify(error, null, 2) ||
|
||||
'An error occurred while exporting from Google Drive'
|
||||
)
|
||||
},
|
||||
}
|
||||
170
apps/sim/tools/google_drive/get_content.ts
Normal file
170
apps/sim/tools/google_drive/get_content.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { ToolConfig } from '../types'
|
||||
import { GoogleDriveGetContentResponse, GoogleDriveToolParams } from './types'
|
||||
import { DEFAULT_EXPORT_FORMATS, GOOGLE_WORKSPACE_MIME_TYPES } from './utils'
|
||||
|
||||
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 (exports Google Workspace files automatically)',
|
||||
version: '1.0',
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'google-drive',
|
||||
additionalScopes: ['https://www.googleapis.com/auth/drive.file'],
|
||||
},
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The access token for the Google Drive API',
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The ID of the file to get content from',
|
||||
},
|
||||
mimeType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'The MIME type to export Google Workspace files to (optional)',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://www.googleapis.com/drive/v3/files/${params.fileId}?fields=id,name,mimeType`,
|
||||
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 = 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)}`,
|
||||
{
|
||||
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`,
|
||||
{
|
||||
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 metadataResponse = await fetch(
|
||||
`https://www.googleapis.com/drive/v3/files/${fileId}?fields=id,name,mimeType,webViewLink,webContentLink,size,createdTime,modifiedTime,parents`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!metadataResponse.ok) {
|
||||
logger.warn('Failed to get full metadata, using partial metadata', {
|
||||
status: metadataResponse.status,
|
||||
statusText: metadataResponse.statusText,
|
||||
})
|
||||
} else {
|
||||
const fullMetadata = await metadataResponse.json()
|
||||
Object.assign(metadata, fullMetadata)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: metadata.id,
|
||||
name: metadata.name,
|
||||
mimeType: metadata.mimeType,
|
||||
webViewLink: metadata.webViewLink,
|
||||
webContentLink: metadata.webContentLink,
|
||||
size: metadata.size,
|
||||
createdTime: metadata.createdTime,
|
||||
modifiedTime: metadata.modifiedTime,
|
||||
parents: metadata.parents,
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('Error in transform response', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
},
|
||||
transformError: (error) => {
|
||||
logger.error('Download error', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
})
|
||||
return error.message || 'An error occurred while getting content from Google Drive'
|
||||
},
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { downloadTool } from './download'
|
||||
import { createFolderTool } from './create_folder'
|
||||
import { getContentTool } from './get_content'
|
||||
import { listTool } from './list'
|
||||
import { uploadTool } from './upload'
|
||||
|
||||
export const driveDownloadTool = downloadTool
|
||||
export const driveCreateFolderTool = createFolderTool
|
||||
export const driveGetContentTool = getContentTool
|
||||
export const driveListTool = listTool
|
||||
export const driveUploadTool = uploadTool
|
||||
|
||||
@@ -40,8 +40,9 @@ export const listTool: ToolConfig<GoogleDriveToolParams, GoogleDriveListResponse
|
||||
|
||||
// Build the query conditions
|
||||
const conditions = ['trashed = false'] // Always exclude trashed files
|
||||
if (params.folderId) {
|
||||
conditions.push(`'${params.folderId}' in parents`)
|
||||
const folderId = params.folderId || params.folderSelector
|
||||
if (folderId) {
|
||||
conditions.push(`'${folderId}' in parents`)
|
||||
}
|
||||
|
||||
// Combine all conditions with AND
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface GoogleDriveUploadResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface GoogleDriveDownloadResponse extends ToolResponse {
|
||||
export interface GoogleDriveGetContentResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: GoogleDriveFile
|
||||
@@ -35,6 +35,7 @@ export interface GoogleDriveDownloadResponse extends ToolResponse {
|
||||
export interface GoogleDriveToolParams {
|
||||
accessToken: string
|
||||
folderId?: string
|
||||
folderSelector?: string
|
||||
fileId?: string
|
||||
fileName?: string
|
||||
content?: string
|
||||
@@ -42,4 +43,5 @@ export interface GoogleDriveToolParams {
|
||||
query?: string
|
||||
pageSize?: number
|
||||
pageToken?: string
|
||||
exportMimeType?: string
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { ToolConfig } from '../types'
|
||||
import { GoogleDriveToolParams, GoogleDriveUploadResponse } from './types'
|
||||
import { GOOGLE_WORKSPACE_MIME_TYPES, SOURCE_MIME_TYPES } from './utils'
|
||||
|
||||
const logger = createLogger('GoogleDriveUploadTool')
|
||||
|
||||
export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResponse> = {
|
||||
id: 'google_drive_upload',
|
||||
@@ -31,59 +35,149 @@ export const uploadTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResp
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart',
|
||||
url: 'https://www.googleapis.com/drive/v3/files',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'multipart/related; boundary=boundary',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const metadata = {
|
||||
name: params.fileName,
|
||||
...(params.folderId ? { parents: [params.folderId] } : {}),
|
||||
name: params.fileName, // Important: Always include the filename in metadata
|
||||
mimeType: params.mimeType || 'text/plain',
|
||||
...(params.folderId && params.folderId.trim() !== '' ? { parents: [params.folderId] } : {}),
|
||||
}
|
||||
|
||||
const mimeType = params.mimeType || 'text/plain'
|
||||
if (params.folderSelector) {
|
||||
metadata.parents = [params.folderSelector]
|
||||
}
|
||||
|
||||
const body = `--boundary
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
${JSON.stringify(metadata)}
|
||||
|
||||
--boundary
|
||||
Content-Type: ${mimeType}
|
||||
|
||||
${params.content}
|
||||
--boundary--`
|
||||
|
||||
return { body }
|
||||
return metadata
|
||||
},
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
transformResponse: async (response: Response, params?: GoogleDriveToolParams) => {
|
||||
try {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || 'Failed to upload file to Google Drive')
|
||||
}
|
||||
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')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
file: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
mimeType: data.mimeType,
|
||||
webViewLink: data.webViewLink,
|
||||
webContentLink: data.webContentLink,
|
||||
size: data.size,
|
||||
createdTime: data.createdTime,
|
||||
modifiedTime: data.modifiedTime,
|
||||
parents: data.parents,
|
||||
// Now upload content to the created file
|
||||
const fileId = data.id
|
||||
const requestedMimeType = params?.mimeType || 'text/plain'
|
||||
const authHeader =
|
||||
response.headers.get('Authorization') || `Bearer ${params?.accessToken || ''}`
|
||||
|
||||
// For Google Workspace formats, use the appropriate source MIME type for content upload
|
||||
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`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
'Content-Type': uploadMimeType,
|
||||
},
|
||||
body: 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')
|
||||
}
|
||||
|
||||
// For Google Workspace documents, update the name again to ensure it sticks after conversion
|
||||
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}`,
|
||||
{
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get the final file data
|
||||
const finalFileResponse = await fetch(
|
||||
`https://www.googleapis.com/drive/v3/files/${fileId}?fields=id,name,mimeType,webViewLink,webContentLink,size,createdTime,modifiedTime,parents`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: authHeader,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const finalFile = await finalFileResponse.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
file: {
|
||||
id: finalFile.id,
|
||||
name: finalFile.name,
|
||||
mimeType: finalFile.mimeType,
|
||||
webViewLink: finalFile.webViewLink,
|
||||
webContentLink: finalFile.webContentLink,
|
||||
size: finalFile.size,
|
||||
createdTime: finalFile.createdTime,
|
||||
modifiedTime: finalFile.modifiedTime,
|
||||
parents: finalFile.parents,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error('Error in upload transformation', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
},
|
||||
transformError: (error) => {
|
||||
logger.error('Upload error', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
})
|
||||
return error.message || 'An error occurred while uploading to Google Drive'
|
||||
},
|
||||
}
|
||||
|
||||
23
apps/sim/tools/google_drive/utils.ts
Normal file
23
apps/sim/tools/google_drive/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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/pdf',
|
||||
'application/vnd.google-apps.script': 'application/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',
|
||||
}
|
||||
@@ -28,7 +28,12 @@ import {
|
||||
import { gmailReadTool, gmailSearchTool, gmailSendTool } from './gmail'
|
||||
import { searchTool as googleSearchTool } from './google'
|
||||
import { docsCreateTool, docsReadTool, docsWriteTool } from './google_docs'
|
||||
import { driveDownloadTool, driveListTool, driveUploadTool } from './google_drive'
|
||||
import {
|
||||
driveCreateFolderTool,
|
||||
driveGetContentTool,
|
||||
driveListTool,
|
||||
driveUploadTool,
|
||||
} from './google_drive'
|
||||
import {
|
||||
sheetsAppendTool,
|
||||
sheetsReadTool,
|
||||
@@ -42,7 +47,7 @@ import { readUrlTool } from './jina'
|
||||
import { jiraBulkRetrieveTool, jiraRetrieveTool, jiraUpdateTool, jiraWriteTool } from './jira'
|
||||
import { linkupSearchTool } from './linkup'
|
||||
import { mem0AddMemoriesTool, mem0GetMemoriesTool, mem0SearchMemoriesTool } from './mem0'
|
||||
import { memoryAddTool, memoryGetTool, memoryGetAllTool, memoryDeleteTool } from './memory'
|
||||
import { memoryAddTool, memoryDeleteTool, memoryGetAllTool, memoryGetTool } from './memory'
|
||||
import { mistralParserTool } from './mistral'
|
||||
import { notionReadTool, notionWriteTool } from './notion'
|
||||
import { dalleTool, embeddingsTool as openAIEmbeddings } from './openai'
|
||||
@@ -127,9 +132,10 @@ export const tools: Record<string, ToolConfig> = {
|
||||
reddit_hot_posts: redditHotPostsTool,
|
||||
reddit_get_posts: redditGetPostsTool,
|
||||
reddit_get_comments: redditGetCommentsTool,
|
||||
google_drive_download: driveDownloadTool,
|
||||
google_drive_get_content: driveGetContentTool,
|
||||
google_drive_list: driveListTool,
|
||||
google_drive_upload: driveUploadTool,
|
||||
google_drive_create_folder: driveCreateFolderTool,
|
||||
google_docs_read: docsReadTool,
|
||||
google_docs_write: docsWriteTool,
|
||||
google_docs_create: docsCreateTool,
|
||||
|
||||
Reference in New Issue
Block a user