From dd4e18b1efde4342974aaa8f0ccd43b62b20f9c2 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 08:07:37 -0700 Subject: [PATCH 1/6] fix(dropbox): align integration with Dropbox API docs, add revision/sharing tools - fix search root-path bug: Dropbox requires "" not "/" for root, same fix already applied to list_folder - fix create_shared_link to return the existing link's metadata (url) when Dropbox reports shared_link_already_exists with matching settings, instead of just erroring - fix upload route autorename default (was true, Dropbox's documented default is false) - trim() all path/fromPath/toPath/rev params to guard against copy-pasted whitespace - mark nullable output fields (id, size, path_display, etc.) optional: true so folder/deleted items don't imply always-present file fields - widen path_display/path_lower types to optional, matching the real API - add dropbox_list_shared_links, dropbox_list_revisions, dropbox_restore tools + block wiring, filling gaps flagged during the audit (revision history/version recovery, and a companion to create_shared_link for looking up existing links) --- .../sim/app/api/tools/dropbox/upload/route.ts | 2 +- apps/sim/blocks/blocks/dropbox.ts | 83 ++++++++++++- apps/sim/tools/dropbox/copy.ts | 10 +- apps/sim/tools/dropbox/create_folder.ts | 2 +- apps/sim/tools/dropbox/create_shared_link.ts | 24 +++- apps/sim/tools/dropbox/delete.ts | 4 +- apps/sim/tools/dropbox/download.ts | 4 +- apps/sim/tools/dropbox/get_metadata.ts | 30 +++-- apps/sim/tools/dropbox/index.ts | 6 + apps/sim/tools/dropbox/list_folder.ts | 6 +- apps/sim/tools/dropbox/list_revisions.ts | 101 ++++++++++++++++ apps/sim/tools/dropbox/list_shared_links.ts | 111 ++++++++++++++++++ apps/sim/tools/dropbox/move.ts | 10 +- apps/sim/tools/dropbox/restore.ts | 82 +++++++++++++ apps/sim/tools/dropbox/search.ts | 3 +- apps/sim/tools/dropbox/types.ts | 70 ++++++++++- apps/sim/tools/dropbox/upload.ts | 8 +- apps/sim/tools/registry.ts | 6 + 18 files changed, 518 insertions(+), 44 deletions(-) create mode 100644 apps/sim/tools/dropbox/list_revisions.ts create mode 100644 apps/sim/tools/dropbox/list_shared_links.ts create mode 100644 apps/sim/tools/dropbox/restore.ts diff --git a/apps/sim/app/api/tools/dropbox/upload/route.ts b/apps/sim/app/api/tools/dropbox/upload/route.ts index c68967b839b..58873c334bc 100644 --- a/apps/sim/app/api/tools/dropbox/upload/route.ts +++ b/apps/sim/app/api/tools/dropbox/upload/route.ts @@ -89,7 +89,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const dropboxApiArg = { path: finalPath, mode: validatedData.mode || 'add', - autorename: validatedData.autorename ?? true, + autorename: validatedData.autorename ?? false, mute: validatedData.mute ?? false, } diff --git a/apps/sim/blocks/blocks/dropbox.ts b/apps/sim/blocks/blocks/dropbox.ts index e18b895a6b9..90aabdb281b 100644 --- a/apps/sim/blocks/blocks/dropbox.ts +++ b/apps/sim/blocks/blocks/dropbox.ts @@ -33,7 +33,10 @@ export const DropboxBlock: BlockConfig = { { label: 'Move File/Folder', id: 'dropbox_move' }, { label: 'Get Metadata', id: 'dropbox_get_metadata' }, { label: 'Create Shared Link', id: 'dropbox_create_shared_link' }, + { label: 'List Shared Links', id: 'dropbox_list_shared_links' }, { label: 'Search Files', id: 'dropbox_search' }, + { label: 'List Revisions', id: 'dropbox_list_revisions' }, + { label: 'Restore File', id: 'dropbox_restore' }, ], value: () => 'dropbox_upload', }, @@ -299,6 +302,55 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, placeholder: '100', condition: { field: 'operation', value: 'dropbox_search' }, }, + // List shared links operation inputs + { + id: 'path', + title: 'Path', + type: 'short-input', + placeholder: '/ (all links) or /folder', + condition: { field: 'operation', value: 'dropbox_list_shared_links' }, + }, + { + id: 'directOnly', + title: 'Direct Links Only', + type: 'switch', + condition: { field: 'operation', value: 'dropbox_list_shared_links' }, + mode: 'advanced', + }, + // List revisions operation inputs + { + id: 'path', + title: 'File Path', + type: 'short-input', + placeholder: '/folder/document.pdf', + condition: { field: 'operation', value: 'dropbox_list_revisions' }, + required: true, + }, + { + id: 'limit', + title: 'Maximum Revisions', + type: 'short-input', + placeholder: '10', + condition: { field: 'operation', value: 'dropbox_list_revisions' }, + mode: 'advanced', + }, + // Restore operation inputs + { + id: 'path', + title: 'File Path', + type: 'short-input', + placeholder: '/folder/document.pdf', + condition: { field: 'operation', value: 'dropbox_restore' }, + required: true, + }, + { + id: 'rev', + title: 'Revision', + type: 'short-input', + placeholder: 'a1c10ce0dd78', + condition: { field: 'operation', value: 'dropbox_restore' }, + required: true, + }, ], tools: { access: [ @@ -311,7 +363,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, 'dropbox_move', 'dropbox_get_metadata', 'dropbox_create_shared_link', + 'dropbox_list_shared_links', 'dropbox_search', + 'dropbox_list_revisions', + 'dropbox_restore', ], config: { tool: (params) => { @@ -334,8 +389,14 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, return 'dropbox_get_metadata' case 'dropbox_create_shared_link': return 'dropbox_create_shared_link' + case 'dropbox_list_shared_links': + return 'dropbox_list_shared_links' case 'dropbox_search': return 'dropbox_search' + case 'dropbox_list_revisions': + return 'dropbox_list_revisions' + case 'dropbox_restore': + return 'dropbox_restore' default: return 'dropbox_upload' } @@ -379,14 +440,21 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, query: { type: 'string', description: 'Search query' }, fileExtensions: { type: 'string', description: 'File extensions filter' }, maxResults: { type: 'number', description: 'Maximum search results' }, + // List shared links inputs + directOnly: { type: 'boolean', description: 'Only return direct links, not parent folders' }, + // Restore input + rev: { type: 'string', description: 'Revision identifier to restore' }, }, outputs: { // Upload/Download outputs file: { type: 'file', description: 'Downloaded file stored in execution files' }, content: { type: 'string', description: 'File content (base64)' }, temporaryLink: { type: 'string', description: 'Temporary download link' }, - // List folder outputs - entries: { type: 'json', description: 'List of files and folders' }, + // List folder / List revisions outputs + entries: { + type: 'json', + description: 'List of files and folders (List Folder), or file revisions (List Revisions)', + }, cursor: { type: 'string', description: 'Pagination cursor' }, hasMore: { type: 'boolean', description: 'Whether more results exist' }, // Create folder output @@ -399,6 +467,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, sharedLink: { type: 'json', description: 'Shared link details' }, // Search outputs matches: { type: 'json', description: 'Search results' }, + // List shared links outputs + links: { type: 'json', description: 'List of shared links (url, name, path_lower, expires)' }, + // List revisions output + isDeleted: { type: 'boolean', description: 'Whether the latest revision is deleted or moved' }, }, } @@ -501,5 +573,12 @@ export const DropboxBlockMeta = { content: '# Share Dropbox Link\n\nGenerate a shareable link for an existing Dropbox item with the right access controls.\n\n## Steps\n1. Confirm the exact path of the file or folder. Use Get Metadata to verify it exists.\n2. Call Create Shared Link with the path and the requested visibility — public for anyone, team-only for internal sharing, or password-protected with a supplied password.\n3. Set an expiration date if the link should not be permanent.\n\n## Output\nReturn the shared link URL, its visibility setting, and the expiration date if one was applied.', }, + { + name: 'recover-dropbox-file-version', + description: + 'Recover an earlier version of a Dropbox file after an unwanted overwrite or edit.', + content: + '# Recover Dropbox File Version\n\nRoll a file back to an earlier saved revision.\n\n## Steps\n1. Call List Revisions on the file path to see prior versions, each with a revision identifier and modification time.\n2. Identify the revision to recover based on its timestamp or size.\n3. Call Restore File with the file path and the chosen revision identifier. This saves that revision as the new current version — it does not delete history.\n\n## Output\nReturn the restored file metadata, including its path and the revision that was restored.', + }, ], } as const satisfies BlockMeta diff --git a/apps/sim/tools/dropbox/copy.ts b/apps/sim/tools/dropbox/copy.ts index 8a6471f0314..16fc7b27610 100644 --- a/apps/sim/tools/dropbox/copy.ts +++ b/apps/sim/tools/dropbox/copy.ts @@ -46,8 +46,8 @@ export const dropboxCopyTool: ToolConfig } }, body: (params) => ({ - from_path: params.fromPath, - to_path: params.toPath, + from_path: params.fromPath.trim(), + to_path: params.toPath.trim(), autorename: params.autorename ?? false, }), }, @@ -77,10 +77,10 @@ export const dropboxCopyTool: ToolConfig description: 'Metadata of the copied item', properties: { '.tag': { type: 'string', description: 'Type: file or folder' }, - id: { type: 'string', description: 'Unique identifier' }, + id: { type: 'string', description: 'Unique identifier', optional: true }, name: { type: 'string', description: 'Name of the copied item' }, - path_display: { type: 'string', description: 'Display path' }, - size: { type: 'number', description: 'Size in bytes (files only)' }, + path_display: { type: 'string', description: 'Display path', optional: true }, + size: { type: 'number', description: 'Size in bytes (files only)', optional: true }, }, }, }, diff --git a/apps/sim/tools/dropbox/create_folder.ts b/apps/sim/tools/dropbox/create_folder.ts index a107747c7d4..5ab54c77362 100644 --- a/apps/sim/tools/dropbox/create_folder.ts +++ b/apps/sim/tools/dropbox/create_folder.ts @@ -43,7 +43,7 @@ export const dropboxCreateFolderTool: ToolConfig< } }, body: (params) => ({ - path: params.path, + path: params.path.trim(), autorename: params.autorename ?? false, }), }, diff --git a/apps/sim/tools/dropbox/create_shared_link.ts b/apps/sim/tools/dropbox/create_shared_link.ts index e27df0451a5..1cd99404168 100644 --- a/apps/sim/tools/dropbox/create_shared_link.ts +++ b/apps/sim/tools/dropbox/create_shared_link.ts @@ -59,7 +59,7 @@ export const dropboxCreateSharedLinkTool: ToolConfig< }, body: (params) => { const body: Record = { - path: params.path, + path: params.path.trim(), } const settings: Record = {} @@ -88,12 +88,22 @@ export const dropboxCreateSharedLinkTool: ToolConfig< const data = await response.json() if (!response.ok) { - // Check if a shared link already exists + // A link may already exist for this path - Dropbox includes its metadata in the error + // when the requested settings match the existing link, so surface it as a success. + const existingLink = data.error?.shared_link_already_exists?.metadata + if (existingLink) { + return { + success: true, + output: { + sharedLink: existingLink, + }, + } + } if (data.error_summary?.includes('shared_link_already_exists')) { return { success: false, error: - 'A shared link already exists for this path. Use list_shared_links to get the existing link.', + 'A shared link already exists for this path with different settings. Use Dropbox List Shared Links to retrieve it.', output: {}, } } @@ -119,8 +129,12 @@ export const dropboxCreateSharedLinkTool: ToolConfig< properties: { url: { type: 'string', description: 'The shared link URL' }, name: { type: 'string', description: 'Name of the shared item' }, - path_lower: { type: 'string', description: 'Lowercase path of the shared item' }, - expires: { type: 'string', description: 'Expiration date if set' }, + path_lower: { + type: 'string', + description: 'Lowercase path of the shared item', + optional: true, + }, + expires: { type: 'string', description: 'Expiration date if set', optional: true }, link_permissions: { type: 'object', description: 'Permissions for the shared link', diff --git a/apps/sim/tools/dropbox/delete.ts b/apps/sim/tools/dropbox/delete.ts index d1e6a67c86d..1e700eb4dd3 100644 --- a/apps/sim/tools/dropbox/delete.ts +++ b/apps/sim/tools/dropbox/delete.ts @@ -34,7 +34,7 @@ export const dropboxDeleteTool: ToolConfig ({ - path: params.path, + path: params.path.trim(), }), }, @@ -65,7 +65,7 @@ export const dropboxDeleteTool: ToolConfig ({ - path: params.path, + path: params.path.trim(), include_media_info: params.includeMediaInfo ?? false, include_deleted: params.includeDeleted ?? false, }), @@ -80,15 +80,27 @@ export const dropboxGetMetadataTool: ToolConfig< description: 'Metadata for the file or folder', properties: { '.tag': { type: 'string', description: 'Type: file, folder, or deleted' }, - id: { type: 'string', description: 'Unique identifier' }, + id: { type: 'string', description: 'Unique identifier', optional: true }, name: { type: 'string', description: 'Name of the item' }, - path_display: { type: 'string', description: 'Display path' }, - path_lower: { type: 'string', description: 'Lowercase path' }, - size: { type: 'number', description: 'Size in bytes (files only)' }, - client_modified: { type: 'string', description: 'Client modification time' }, - server_modified: { type: 'string', description: 'Server modification time' }, - rev: { type: 'string', description: 'Revision identifier' }, - content_hash: { type: 'string', description: 'Content hash' }, + path_display: { type: 'string', description: 'Display path', optional: true }, + path_lower: { type: 'string', description: 'Lowercase path', optional: true }, + size: { type: 'number', description: 'Size in bytes (files only)', optional: true }, + client_modified: { + type: 'string', + description: 'Client modification time (files only)', + optional: true, + }, + server_modified: { + type: 'string', + description: 'Server modification time (files only)', + optional: true, + }, + rev: { type: 'string', description: 'Revision identifier (files only)', optional: true }, + content_hash: { + type: 'string', + description: 'Content hash (files only)', + optional: true, + }, }, }, }, diff --git a/apps/sim/tools/dropbox/index.ts b/apps/sim/tools/dropbox/index.ts index 6fea8749700..c113cc121af 100644 --- a/apps/sim/tools/dropbox/index.ts +++ b/apps/sim/tools/dropbox/index.ts @@ -5,7 +5,10 @@ import { dropboxDeleteTool } from '@/tools/dropbox/delete' import { dropboxDownloadTool } from '@/tools/dropbox/download' import { dropboxGetMetadataTool } from '@/tools/dropbox/get_metadata' import { dropboxListFolderTool } from '@/tools/dropbox/list_folder' +import { dropboxListRevisionsTool } from '@/tools/dropbox/list_revisions' +import { dropboxListSharedLinksTool } from '@/tools/dropbox/list_shared_links' import { dropboxMoveTool } from '@/tools/dropbox/move' +import { dropboxRestoreTool } from '@/tools/dropbox/restore' import { dropboxSearchTool } from '@/tools/dropbox/search' import { dropboxUploadTool } from '@/tools/dropbox/upload' @@ -17,7 +20,10 @@ export { dropboxDownloadTool, dropboxGetMetadataTool, dropboxListFolderTool, + dropboxListRevisionsTool, + dropboxListSharedLinksTool, dropboxMoveTool, + dropboxRestoreTool, dropboxSearchTool, dropboxUploadTool, } diff --git a/apps/sim/tools/dropbox/list_folder.ts b/apps/sim/tools/dropbox/list_folder.ts index 19e30158f67..466f3d11c64 100644 --- a/apps/sim/tools/dropbox/list_folder.ts +++ b/apps/sim/tools/dropbox/list_folder.ts @@ -59,7 +59,7 @@ export const dropboxListFolderTool: ToolConfig ({ - path: params.path === '/' ? '' : params.path, + path: params.path.trim() === '/' ? '' : params.path.trim(), recursive: params.recursive ?? false, include_deleted: params.includeDeleted ?? false, include_media_info: params.includeMediaInfo ?? false, @@ -96,10 +96,10 @@ export const dropboxListFolderTool: ToolConfig = { + id: 'dropbox_list_revisions', + name: 'Dropbox List Revisions', + description: 'List the revision history for a file in Dropbox (files only, not folders)', + version: '1.0.0', + + oauth: { + required: true, + provider: 'dropbox', + }, + + params: { + path: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The path of the file to list revisions for', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-only', + description: 'Maximum number of revisions to return, 1-100 (default: 10)', + }, + }, + + request: { + url: 'https://api.dropboxapi.com/2/files/list_revisions', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Missing access token for Dropbox API request') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => ({ + path: params.path.trim(), + mode: 'path', + limit: params.limit ?? 10, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data.error_summary || data.error?.message || 'Failed to list revisions', + output: {}, + } + } + + return { + success: true, + output: { + entries: data.entries || [], + isDeleted: data.is_deleted ?? false, + hasMore: data.has_more ?? false, + }, + } + }, + + outputs: { + entries: { + type: 'array', + description: 'The revisions for the file, most recent first', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Unique identifier for this revision' }, + name: { type: 'string', description: 'Name of the file' }, + path_display: { type: 'string', description: 'Display path' }, + rev: { type: 'string', description: 'Revision identifier, pass to Restore' }, + size: { type: 'number', description: 'Size of this revision in bytes' }, + server_modified: { type: 'string', description: 'Server modification time' }, + }, + }, + }, + isDeleted: { + type: 'boolean', + description: 'Whether the file identified by the latest revision is deleted or moved', + }, + hasMore: { + type: 'boolean', + description: 'Whether there are more revisions available', + }, + }, +} diff --git a/apps/sim/tools/dropbox/list_shared_links.ts b/apps/sim/tools/dropbox/list_shared_links.ts new file mode 100644 index 00000000000..70c84b75194 --- /dev/null +++ b/apps/sim/tools/dropbox/list_shared_links.ts @@ -0,0 +1,111 @@ +import type { + DropboxListSharedLinksParams, + DropboxListSharedLinksResponse, +} from '@/tools/dropbox/types' +import type { ToolConfig } from '@/tools/types' + +export const dropboxListSharedLinksTool: ToolConfig< + DropboxListSharedLinksParams, + DropboxListSharedLinksResponse +> = { + id: 'dropbox_list_shared_links', + name: 'Dropbox List Shared Links', + description: 'List shared links for a path, or for the entire account if no path is given', + version: '1.0.0', + + oauth: { + required: true, + provider: 'dropbox', + }, + + params: { + path: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Path to list shared links for. If omitted, lists all shared links.', + }, + directOnly: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'If true, only return links directly to the path, not parent folder links', + }, + cursor: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Cursor from a previous call to fetch the next page of results', + }, + }, + + request: { + url: 'https://api.dropboxapi.com/2/sharing/list_shared_links', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Missing access token for Dropbox API request') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => { + const body: Record = {} + if (params.path) { + const trimmedPath = params.path.trim() + body.path = trimmedPath === '/' ? '' : trimmedPath + } + if (params.directOnly !== undefined) body.direct_only = params.directOnly + if (params.cursor) body.cursor = params.cursor + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data.error_summary || data.error?.message || 'Failed to list shared links', + output: {}, + } + } + + return { + success: true, + output: { + links: data.links || [], + hasMore: data.has_more || false, + cursor: data.cursor, + }, + } + }, + + outputs: { + links: { + type: 'array', + description: 'Shared links applicable to the path argument', + items: { + type: 'object', + properties: { + '.tag': { type: 'string', description: 'Type: file or folder' }, + url: { type: 'string', description: 'The shared link URL' }, + name: { type: 'string', description: 'Name of the shared item' }, + path_lower: { type: 'string', description: 'Lowercase path of the shared item' }, + expires: { type: 'string', description: 'Expiration date if set' }, + }, + }, + }, + hasMore: { + type: 'boolean', + description: 'Whether there are more results', + }, + cursor: { + type: 'string', + description: 'Cursor for pagination (only returned when no path is given)', + }, + }, +} diff --git a/apps/sim/tools/dropbox/move.ts b/apps/sim/tools/dropbox/move.ts index bbdf57081f4..3d11c5a53d0 100644 --- a/apps/sim/tools/dropbox/move.ts +++ b/apps/sim/tools/dropbox/move.ts @@ -46,8 +46,8 @@ export const dropboxMoveTool: ToolConfig } }, body: (params) => ({ - from_path: params.fromPath, - to_path: params.toPath, + from_path: params.fromPath.trim(), + to_path: params.toPath.trim(), autorename: params.autorename ?? false, }), }, @@ -77,10 +77,10 @@ export const dropboxMoveTool: ToolConfig description: 'Metadata of the moved item', properties: { '.tag': { type: 'string', description: 'Type: file or folder' }, - id: { type: 'string', description: 'Unique identifier' }, + id: { type: 'string', description: 'Unique identifier', optional: true }, name: { type: 'string', description: 'Name of the moved item' }, - path_display: { type: 'string', description: 'Display path' }, - size: { type: 'number', description: 'Size in bytes (files only)' }, + path_display: { type: 'string', description: 'Display path', optional: true }, + size: { type: 'number', description: 'Size in bytes (files only)', optional: true }, }, }, }, diff --git a/apps/sim/tools/dropbox/restore.ts b/apps/sim/tools/dropbox/restore.ts new file mode 100644 index 00000000000..8ae5f4ad25e --- /dev/null +++ b/apps/sim/tools/dropbox/restore.ts @@ -0,0 +1,82 @@ +import type { DropboxRestoreParams, DropboxRestoreResponse } from '@/tools/dropbox/types' +import type { ToolConfig } from '@/tools/types' + +export const dropboxRestoreTool: ToolConfig = { + id: 'dropbox_restore', + name: 'Dropbox Restore', + description: 'Restore a specific revision of a file to the given path', + version: '1.0.0', + + oauth: { + required: true, + provider: 'dropbox', + }, + + params: { + path: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The path to save the restored file to', + }, + rev: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The revision identifier to restore (from Dropbox List Revisions)', + }, + }, + + request: { + url: 'https://api.dropboxapi.com/2/files/restore', + method: 'POST', + headers: (params) => { + if (!params.accessToken) { + throw new Error('Missing access token for Dropbox API request') + } + return { + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + } + }, + body: (params) => ({ + path: params.path.trim(), + rev: params.rev.trim(), + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + return { + success: false, + error: data.error_summary || data.error?.message || 'Failed to restore file', + output: {}, + } + } + + return { + success: true, + output: { + metadata: data, + }, + } + }, + + outputs: { + metadata: { + type: 'object', + description: 'Metadata of the restored file', + properties: { + id: { type: 'string', description: 'Unique identifier for the file' }, + name: { type: 'string', description: 'Name of the file' }, + path_display: { type: 'string', description: 'Display path of the file' }, + path_lower: { type: 'string', description: 'Lowercase path of the file' }, + size: { type: 'number', description: 'Size of the file in bytes' }, + rev: { type: 'string', description: 'Revision identifier of the restored file' }, + server_modified: { type: 'string', description: 'Server modification time' }, + }, + }, + }, +} diff --git a/apps/sim/tools/dropbox/search.ts b/apps/sim/tools/dropbox/search.ts index 558f89fc186..4498a44d31a 100644 --- a/apps/sim/tools/dropbox/search.ts +++ b/apps/sim/tools/dropbox/search.ts @@ -59,7 +59,8 @@ export const dropboxSearchTool: ToolConfig = {} if (params.path) { - options.path = params.path + const trimmedPath = params.path.trim() + options.path = trimmedPath === '/' ? '' : trimmedPath } if (params.fileExtensions) { diff --git a/apps/sim/tools/dropbox/types.ts b/apps/sim/tools/dropbox/types.ts index 94d8bd20054..479b0599f61 100644 --- a/apps/sim/tools/dropbox/types.ts +++ b/apps/sim/tools/dropbox/types.ts @@ -7,8 +7,8 @@ interface DropboxFileMetadata { '.tag': 'file' id: string name: string - path_display: string - path_lower: string + path_display?: string + path_lower?: string size: number client_modified: string server_modified: string @@ -21,15 +21,15 @@ interface DropboxFolderMetadata { '.tag': 'folder' id: string name: string - path_display: string - path_lower: string + path_display?: string + path_lower?: string } interface DropboxDeletedMetadata { '.tag': 'deleted' name: string - path_display: string - path_lower: string + path_display?: string + path_lower?: string } export type DropboxMetadata = DropboxFileMetadata | DropboxFolderMetadata | DropboxDeletedMetadata @@ -232,6 +232,61 @@ interface DropboxGetTemporaryLinkResponse extends ToolResponse { } } +// ===== List Shared Links Params ===== + +export interface DropboxListSharedLinksParams extends DropboxBaseParams { + path?: string + directOnly?: boolean + cursor?: string +} + +export interface DropboxListSharedLinksResponse extends ToolResponse { + output: { + links?: DropboxSharedLinkMetadata[] + hasMore?: boolean + cursor?: string + } +} + +// ===== List Revisions Params ===== + +interface DropboxFileRevision { + '.tag': 'file' + id: string + name: string + path_display?: string + path_lower?: string + size: number + rev: string + server_modified: string +} + +export interface DropboxListRevisionsParams extends DropboxBaseParams { + path: string + limit?: number +} + +export interface DropboxListRevisionsResponse extends ToolResponse { + output: { + entries?: DropboxFileRevision[] + isDeleted?: boolean + hasMore?: boolean + } +} + +// ===== Restore Params ===== + +export interface DropboxRestoreParams extends DropboxBaseParams { + path: string + rev: string +} + +export interface DropboxRestoreResponse extends ToolResponse { + output: { + metadata?: DropboxFileMetadata + } +} + // ===== Combined Response Type ===== export type DropboxResponse = @@ -246,3 +301,6 @@ export type DropboxResponse = | DropboxCreateSharedLinkResponse | DropboxSearchResponse | DropboxGetTemporaryLinkResponse + | DropboxListSharedLinksResponse + | DropboxListRevisionsResponse + | DropboxRestoreResponse diff --git a/apps/sim/tools/dropbox/upload.ts b/apps/sim/tools/dropbox/upload.ts index 1a8914bc439..96b1270fc4b 100644 --- a/apps/sim/tools/dropbox/upload.ts +++ b/apps/sim/tools/dropbox/upload.ts @@ -67,7 +67,7 @@ export const dropboxUploadTool: ToolConfig ({ accessToken: params.accessToken, - path: params.path, + path: params.path.trim(), file: params.file, fileContent: params.fileContent, fileName: params.fileName, @@ -107,7 +107,11 @@ export const dropboxUploadTool: ToolConfig = { dropbox_get_metadata: dropboxGetMetadataTool, dropbox_create_shared_link: dropboxCreateSharedLinkTool, dropbox_search: dropboxSearchTool, + dropbox_list_shared_links: dropboxListSharedLinksTool, + dropbox_list_revisions: dropboxListRevisionsTool, + dropbox_restore: dropboxRestoreTool, devin_create_session: devinCreateSessionTool, devin_get_session: devinGetSessionTool, devin_list_sessions: devinListSessionsTool, From 1e7678289f274d50db5aabd813ece5b18a010159 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 08:14:12 -0700 Subject: [PATCH 2/6] style(dropbox): use ?? instead of || for boolean defaults in list_shared_links Consistency nit from Greptile review - matches the ?? pattern already used by every other transformResponse in this PR. --- apps/sim/tools/dropbox/list_shared_links.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/tools/dropbox/list_shared_links.ts b/apps/sim/tools/dropbox/list_shared_links.ts index 70c84b75194..f600bdd0a78 100644 --- a/apps/sim/tools/dropbox/list_shared_links.ts +++ b/apps/sim/tools/dropbox/list_shared_links.ts @@ -77,8 +77,8 @@ export const dropboxListSharedLinksTool: ToolConfig< return { success: true, output: { - links: data.links || [], - hasMore: data.has_more || false, + links: data.links ?? [], + hasMore: data.has_more ?? false, cursor: data.cursor, }, } From 85504716966cacf2068e2c3ea68a5b77699c1d8b Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 08:25:00 -0700 Subject: [PATCH 3/6] fix(dropbox): mark path_display/path_lower optional everywhere for consistency Greptile flagged that path_display was left required in list_folder.ts and list_revisions.ts despite being widened to optional in types.ts and most other output schemas in this PR. Fixed those two plus create_folder.ts, restore.ts, upload.ts, and list_shared_links.ts, which had the same gap. --- apps/sim/tools/dropbox/create_folder.ts | 4 ++-- apps/sim/tools/dropbox/list_folder.ts | 2 +- apps/sim/tools/dropbox/list_revisions.ts | 2 +- apps/sim/tools/dropbox/list_shared_links.ts | 6 +++++- apps/sim/tools/dropbox/restore.ts | 4 ++-- apps/sim/tools/dropbox/upload.ts | 4 ++-- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/sim/tools/dropbox/create_folder.ts b/apps/sim/tools/dropbox/create_folder.ts index 5ab54c77362..b57711591e3 100644 --- a/apps/sim/tools/dropbox/create_folder.ts +++ b/apps/sim/tools/dropbox/create_folder.ts @@ -74,8 +74,8 @@ export const dropboxCreateFolderTool: ToolConfig< properties: { id: { type: 'string', description: 'Unique identifier for the folder' }, name: { type: 'string', description: 'Name of the folder' }, - path_display: { type: 'string', description: 'Display path of the folder' }, - path_lower: { type: 'string', description: 'Lowercase path of the folder' }, + path_display: { type: 'string', description: 'Display path of the folder', optional: true }, + path_lower: { type: 'string', description: 'Lowercase path of the folder', optional: true }, }, }, }, diff --git a/apps/sim/tools/dropbox/list_folder.ts b/apps/sim/tools/dropbox/list_folder.ts index 466f3d11c64..a0a31ba9f1b 100644 --- a/apps/sim/tools/dropbox/list_folder.ts +++ b/apps/sim/tools/dropbox/list_folder.ts @@ -98,7 +98,7 @@ export const dropboxListFolderTool: ToolConfig Date: Tue, 7 Jul 2026 08:34:07 -0700 Subject: [PATCH 4/6] fix(dropbox): list_shared_links path omission + list_revisions pagination Cursor Bugbot flagged two real gaps: - list_shared_links sent path: "" for root/all, but Dropbox only returns every link account-wide when path is omitted entirely; "" scopes to the root folder specifically. Now omits the field for root/empty/"/" input. - list_revisions exposed hasMore but no way to actually fetch more pages (Dropbox's before_rev cursor). Added a beforeRev param + advanced-mode block field. A third flagged issue (create_shared_link's error.shared_link_already_exists.metadata path) was verified against the official sharing.stone spec and confirmed correct as-is - replied on the PR thread with the citation, no change needed. --- apps/sim/blocks/blocks/dropbox.ts | 10 +++++++++ apps/sim/tools/dropbox/list_revisions.ts | 23 ++++++++++++++++----- apps/sim/tools/dropbox/list_shared_links.ts | 7 ++++++- apps/sim/tools/dropbox/types.ts | 1 + 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/apps/sim/blocks/blocks/dropbox.ts b/apps/sim/blocks/blocks/dropbox.ts index 90aabdb281b..886cf662569 100644 --- a/apps/sim/blocks/blocks/dropbox.ts +++ b/apps/sim/blocks/blocks/dropbox.ts @@ -334,6 +334,14 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, condition: { field: 'operation', value: 'dropbox_list_revisions' }, mode: 'advanced', }, + { + id: 'beforeRev', + title: 'Before Revision', + type: 'short-input', + placeholder: 'Revision from a previous call, to fetch the next page', + condition: { field: 'operation', value: 'dropbox_list_revisions' }, + mode: 'advanced', + }, // Restore operation inputs { id: 'path', @@ -442,6 +450,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, maxResults: { type: 'number', description: 'Maximum search results' }, // List shared links inputs directOnly: { type: 'boolean', description: 'Only return direct links, not parent folders' }, + // List revisions input + beforeRev: { type: 'string', description: 'Fetch revisions before this one (pagination)' }, // Restore input rev: { type: 'string', description: 'Revision identifier to restore' }, }, diff --git a/apps/sim/tools/dropbox/list_revisions.ts b/apps/sim/tools/dropbox/list_revisions.ts index b51ee7ecd17..e9bce1f6220 100644 --- a/apps/sim/tools/dropbox/list_revisions.ts +++ b/apps/sim/tools/dropbox/list_revisions.ts @@ -31,6 +31,13 @@ export const dropboxListRevisionsTool: ToolConfig< visibility: 'user-only', description: 'Maximum number of revisions to return, 1-100 (default: 10)', }, + beforeRev: { + type: 'string', + required: false, + visibility: 'user-only', + description: + 'Only return revisions before this one. Pass the rev of the last revision from a previous call to fetch the next page.', + }, }, request: { @@ -45,11 +52,17 @@ export const dropboxListRevisionsTool: ToolConfig< 'Content-Type': 'application/json', } }, - body: (params) => ({ - path: params.path.trim(), - mode: 'path', - limit: params.limit ?? 10, - }), + body: (params) => { + const body: Record = { + path: params.path.trim(), + mode: 'path', + limit: params.limit ?? 10, + } + if (params.beforeRev) { + body.before_rev = params.beforeRev.trim() + } + return body + }, }, transformResponse: async (response) => { diff --git a/apps/sim/tools/dropbox/list_shared_links.ts b/apps/sim/tools/dropbox/list_shared_links.ts index 4316d72f9f3..c1b4f5c8420 100644 --- a/apps/sim/tools/dropbox/list_shared_links.ts +++ b/apps/sim/tools/dropbox/list_shared_links.ts @@ -55,7 +55,12 @@ export const dropboxListSharedLinksTool: ToolConfig< const body: Record = {} if (params.path) { const trimmedPath = params.path.trim() - body.path = trimmedPath === '/' ? '' : trimmedPath + // Dropbox only returns every shared link on the account when `path` is omitted + // entirely; sending "" scopes the results to the root folder instead. Since our UI + // tells users "/" means "list all links", omit the field rather than sending "". + if (trimmedPath !== '/' && trimmedPath !== '') { + body.path = trimmedPath + } } if (params.directOnly !== undefined) body.direct_only = params.directOnly if (params.cursor) body.cursor = params.cursor diff --git a/apps/sim/tools/dropbox/types.ts b/apps/sim/tools/dropbox/types.ts index 479b0599f61..2b9440e6359 100644 --- a/apps/sim/tools/dropbox/types.ts +++ b/apps/sim/tools/dropbox/types.ts @@ -264,6 +264,7 @@ interface DropboxFileRevision { export interface DropboxListRevisionsParams extends DropboxBaseParams { path: string limit?: number + beforeRev?: string } export interface DropboxListRevisionsResponse extends ToolResponse { From e00b2d848b6cf84b44d2b64110713b4d76ddb0fe Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 08:41:07 -0700 Subject: [PATCH 5/6] fix(dropbox): correct shared_link_already_exists JSON path The prior fix read data.error.shared_link_already_exists.metadata, which is wrong per Stone's own JSON serialization rules: a union member whose payload is a struct (SharedLinkMetadata) flattens that struct's fields alongside ".tag" rather than nesting under a wrapper key. The real shape is data.error.shared_link_already_exists directly (with an extra ".tag" field mixed in) - there is no nested .metadata key, so the existing-link fast path never fired before this fix. Also marks list_shared_links' expires output optional, matching every other optional field in this PR and the SharedLinkMetadata spec. --- apps/sim/tools/dropbox/create_shared_link.ts | 4 +++- apps/sim/tools/dropbox/list_shared_links.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/sim/tools/dropbox/create_shared_link.ts b/apps/sim/tools/dropbox/create_shared_link.ts index 1cd99404168..b7b05be3845 100644 --- a/apps/sim/tools/dropbox/create_shared_link.ts +++ b/apps/sim/tools/dropbox/create_shared_link.ts @@ -90,7 +90,9 @@ export const dropboxCreateSharedLinkTool: ToolConfig< if (!response.ok) { // A link may already exist for this path - Dropbox includes its metadata in the error // when the requested settings match the existing link, so surface it as a success. - const existingLink = data.error?.shared_link_already_exists?.metadata + // Per Stone's JSON rules, a union member whose payload is a struct (SharedLinkMetadata) + // flattens that struct's fields alongside ".tag" - there is no nested "metadata" key. + const existingLink = data.error?.shared_link_already_exists if (existingLink) { return { success: true, diff --git a/apps/sim/tools/dropbox/list_shared_links.ts b/apps/sim/tools/dropbox/list_shared_links.ts index c1b4f5c8420..1459424821a 100644 --- a/apps/sim/tools/dropbox/list_shared_links.ts +++ b/apps/sim/tools/dropbox/list_shared_links.ts @@ -104,7 +104,7 @@ export const dropboxListSharedLinksTool: ToolConfig< description: 'Lowercase path of the shared item', optional: true, }, - expires: { type: 'string', description: 'Expiration date if set' }, + expires: { type: 'string', description: 'Expiration date if set', optional: true }, }, }, }, From 3cb41c9589d55c67a317bf2e846781bcf37c526a Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 7 Jul 2026 08:48:06 -0700 Subject: [PATCH 6/6] fix(dropbox): wire cursor pagination for List Shared Links in the block The dropbox_list_shared_links tool already accepted a cursor param, but the block never exposed it, so a workflow couldn't page past the first batch when hasMore was true. Adds an advanced-mode cursor subBlock + input entry, mirroring List Revisions' beforeRev field added in the same PR. --- apps/sim/blocks/blocks/dropbox.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/sim/blocks/blocks/dropbox.ts b/apps/sim/blocks/blocks/dropbox.ts index 886cf662569..f162ce89c6c 100644 --- a/apps/sim/blocks/blocks/dropbox.ts +++ b/apps/sim/blocks/blocks/dropbox.ts @@ -317,6 +317,14 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, condition: { field: 'operation', value: 'dropbox_list_shared_links' }, mode: 'advanced', }, + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + placeholder: 'Cursor from a previous call, to fetch the next page', + condition: { field: 'operation', value: 'dropbox_list_shared_links' }, + mode: 'advanced', + }, // List revisions operation inputs { id: 'path', @@ -450,6 +458,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, maxResults: { type: 'number', description: 'Maximum search results' }, // List shared links inputs directOnly: { type: 'boolean', description: 'Only return direct links, not parent folders' }, + cursor: { type: 'string', description: 'Fetch the next page of shared links (pagination)' }, // List revisions input beforeRev: { type: 'string', description: 'Fetch revisions before this one (pagination)' }, // Restore input