Skip to content

Commit 0132a04

Browse files
authored
fix(dropbox): align integration with Dropbox API docs, add revision/sharing tools (#5468)
* 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) * 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. * 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. * 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. * 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. * 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.
1 parent a1e6744 commit 0132a04

18 files changed

Lines changed: 567 additions & 49 deletions

File tree

apps/sim/app/api/tools/dropbox/upload/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
8989
const dropboxApiArg = {
9090
path: finalPath,
9191
mode: validatedData.mode || 'add',
92-
autorename: validatedData.autorename ?? true,
92+
autorename: validatedData.autorename ?? false,
9393
mute: validatedData.mute ?? false,
9494
}
9595

apps/sim/blocks/blocks/dropbox.ts

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export const DropboxBlock: BlockConfig<DropboxResponse> = {
3333
{ label: 'Move File/Folder', id: 'dropbox_move' },
3434
{ label: 'Get Metadata', id: 'dropbox_get_metadata' },
3535
{ label: 'Create Shared Link', id: 'dropbox_create_shared_link' },
36+
{ label: 'List Shared Links', id: 'dropbox_list_shared_links' },
3637
{ label: 'Search Files', id: 'dropbox_search' },
38+
{ label: 'List Revisions', id: 'dropbox_list_revisions' },
39+
{ label: 'Restore File', id: 'dropbox_restore' },
3740
],
3841
value: () => 'dropbox_upload',
3942
},
@@ -299,6 +302,71 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
299302
placeholder: '100',
300303
condition: { field: 'operation', value: 'dropbox_search' },
301304
},
305+
// List shared links operation inputs
306+
{
307+
id: 'path',
308+
title: 'Path',
309+
type: 'short-input',
310+
placeholder: '/ (all links) or /folder',
311+
condition: { field: 'operation', value: 'dropbox_list_shared_links' },
312+
},
313+
{
314+
id: 'directOnly',
315+
title: 'Direct Links Only',
316+
type: 'switch',
317+
condition: { field: 'operation', value: 'dropbox_list_shared_links' },
318+
mode: 'advanced',
319+
},
320+
{
321+
id: 'cursor',
322+
title: 'Cursor',
323+
type: 'short-input',
324+
placeholder: 'Cursor from a previous call, to fetch the next page',
325+
condition: { field: 'operation', value: 'dropbox_list_shared_links' },
326+
mode: 'advanced',
327+
},
328+
// List revisions operation inputs
329+
{
330+
id: 'path',
331+
title: 'File Path',
332+
type: 'short-input',
333+
placeholder: '/folder/document.pdf',
334+
condition: { field: 'operation', value: 'dropbox_list_revisions' },
335+
required: true,
336+
},
337+
{
338+
id: 'limit',
339+
title: 'Maximum Revisions',
340+
type: 'short-input',
341+
placeholder: '10',
342+
condition: { field: 'operation', value: 'dropbox_list_revisions' },
343+
mode: 'advanced',
344+
},
345+
{
346+
id: 'beforeRev',
347+
title: 'Before Revision',
348+
type: 'short-input',
349+
placeholder: 'Revision from a previous call, to fetch the next page',
350+
condition: { field: 'operation', value: 'dropbox_list_revisions' },
351+
mode: 'advanced',
352+
},
353+
// Restore operation inputs
354+
{
355+
id: 'path',
356+
title: 'File Path',
357+
type: 'short-input',
358+
placeholder: '/folder/document.pdf',
359+
condition: { field: 'operation', value: 'dropbox_restore' },
360+
required: true,
361+
},
362+
{
363+
id: 'rev',
364+
title: 'Revision',
365+
type: 'short-input',
366+
placeholder: 'a1c10ce0dd78',
367+
condition: { field: 'operation', value: 'dropbox_restore' },
368+
required: true,
369+
},
302370
],
303371
tools: {
304372
access: [
@@ -311,7 +379,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
311379
'dropbox_move',
312380
'dropbox_get_metadata',
313381
'dropbox_create_shared_link',
382+
'dropbox_list_shared_links',
314383
'dropbox_search',
384+
'dropbox_list_revisions',
385+
'dropbox_restore',
315386
],
316387
config: {
317388
tool: (params) => {
@@ -334,8 +405,14 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
334405
return 'dropbox_get_metadata'
335406
case 'dropbox_create_shared_link':
336407
return 'dropbox_create_shared_link'
408+
case 'dropbox_list_shared_links':
409+
return 'dropbox_list_shared_links'
337410
case 'dropbox_search':
338411
return 'dropbox_search'
412+
case 'dropbox_list_revisions':
413+
return 'dropbox_list_revisions'
414+
case 'dropbox_restore':
415+
return 'dropbox_restore'
339416
default:
340417
return 'dropbox_upload'
341418
}
@@ -379,14 +456,24 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
379456
query: { type: 'string', description: 'Search query' },
380457
fileExtensions: { type: 'string', description: 'File extensions filter' },
381458
maxResults: { type: 'number', description: 'Maximum search results' },
459+
// List shared links inputs
460+
directOnly: { type: 'boolean', description: 'Only return direct links, not parent folders' },
461+
cursor: { type: 'string', description: 'Fetch the next page of shared links (pagination)' },
462+
// List revisions input
463+
beforeRev: { type: 'string', description: 'Fetch revisions before this one (pagination)' },
464+
// Restore input
465+
rev: { type: 'string', description: 'Revision identifier to restore' },
382466
},
383467
outputs: {
384468
// Upload/Download outputs
385469
file: { type: 'file', description: 'Downloaded file stored in execution files' },
386470
content: { type: 'string', description: 'File content (base64)' },
387471
temporaryLink: { type: 'string', description: 'Temporary download link' },
388-
// List folder outputs
389-
entries: { type: 'json', description: 'List of files and folders' },
472+
// List folder / List revisions outputs
473+
entries: {
474+
type: 'json',
475+
description: 'List of files and folders (List Folder), or file revisions (List Revisions)',
476+
},
390477
cursor: { type: 'string', description: 'Pagination cursor' },
391478
hasMore: { type: 'boolean', description: 'Whether more results exist' },
392479
// Create folder output
@@ -399,6 +486,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
399486
sharedLink: { type: 'json', description: 'Shared link details' },
400487
// Search outputs
401488
matches: { type: 'json', description: 'Search results' },
489+
// List shared links outputs
490+
links: { type: 'json', description: 'List of shared links (url, name, path_lower, expires)' },
491+
// List revisions output
492+
isDeleted: { type: 'boolean', description: 'Whether the latest revision is deleted or moved' },
402493
},
403494
}
404495

@@ -501,5 +592,12 @@ export const DropboxBlockMeta = {
501592
content:
502593
'# 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.',
503594
},
595+
{
596+
name: 'recover-dropbox-file-version',
597+
description:
598+
'Recover an earlier version of a Dropbox file after an unwanted overwrite or edit.',
599+
content:
600+
'# 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.',
601+
},
504602
],
505603
} as const satisfies BlockMeta

apps/sim/tools/dropbox/copy.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export const dropboxCopyTool: ToolConfig<DropboxCopyParams, DropboxCopyResponse>
4646
}
4747
},
4848
body: (params) => ({
49-
from_path: params.fromPath,
50-
to_path: params.toPath,
49+
from_path: params.fromPath.trim(),
50+
to_path: params.toPath.trim(),
5151
autorename: params.autorename ?? false,
5252
}),
5353
},
@@ -77,10 +77,10 @@ export const dropboxCopyTool: ToolConfig<DropboxCopyParams, DropboxCopyResponse>
7777
description: 'Metadata of the copied item',
7878
properties: {
7979
'.tag': { type: 'string', description: 'Type: file or folder' },
80-
id: { type: 'string', description: 'Unique identifier' },
80+
id: { type: 'string', description: 'Unique identifier', optional: true },
8181
name: { type: 'string', description: 'Name of the copied item' },
82-
path_display: { type: 'string', description: 'Display path' },
83-
size: { type: 'number', description: 'Size in bytes (files only)' },
82+
path_display: { type: 'string', description: 'Display path', optional: true },
83+
size: { type: 'number', description: 'Size in bytes (files only)', optional: true },
8484
},
8585
},
8686
},

apps/sim/tools/dropbox/create_folder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const dropboxCreateFolderTool: ToolConfig<
4343
}
4444
},
4545
body: (params) => ({
46-
path: params.path,
46+
path: params.path.trim(),
4747
autorename: params.autorename ?? false,
4848
}),
4949
},
@@ -74,8 +74,8 @@ export const dropboxCreateFolderTool: ToolConfig<
7474
properties: {
7575
id: { type: 'string', description: 'Unique identifier for the folder' },
7676
name: { type: 'string', description: 'Name of the folder' },
77-
path_display: { type: 'string', description: 'Display path of the folder' },
78-
path_lower: { type: 'string', description: 'Lowercase path of the folder' },
77+
path_display: { type: 'string', description: 'Display path of the folder', optional: true },
78+
path_lower: { type: 'string', description: 'Lowercase path of the folder', optional: true },
7979
},
8080
},
8181
},

apps/sim/tools/dropbox/create_shared_link.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const dropboxCreateSharedLinkTool: ToolConfig<
5959
},
6060
body: (params) => {
6161
const body: Record<string, any> = {
62-
path: params.path,
62+
path: params.path.trim(),
6363
}
6464

6565
const settings: Record<string, any> = {}
@@ -88,12 +88,24 @@ export const dropboxCreateSharedLinkTool: ToolConfig<
8888
const data = await response.json()
8989

9090
if (!response.ok) {
91-
// Check if a shared link already exists
91+
// A link may already exist for this path - Dropbox includes its metadata in the error
92+
// when the requested settings match the existing link, so surface it as a success.
93+
// Per Stone's JSON rules, a union member whose payload is a struct (SharedLinkMetadata)
94+
// flattens that struct's fields alongside ".tag" - there is no nested "metadata" key.
95+
const existingLink = data.error?.shared_link_already_exists
96+
if (existingLink) {
97+
return {
98+
success: true,
99+
output: {
100+
sharedLink: existingLink,
101+
},
102+
}
103+
}
92104
if (data.error_summary?.includes('shared_link_already_exists')) {
93105
return {
94106
success: false,
95107
error:
96-
'A shared link already exists for this path. Use list_shared_links to get the existing link.',
108+
'A shared link already exists for this path with different settings. Use Dropbox List Shared Links to retrieve it.',
97109
output: {},
98110
}
99111
}
@@ -119,8 +131,12 @@ export const dropboxCreateSharedLinkTool: ToolConfig<
119131
properties: {
120132
url: { type: 'string', description: 'The shared link URL' },
121133
name: { type: 'string', description: 'Name of the shared item' },
122-
path_lower: { type: 'string', description: 'Lowercase path of the shared item' },
123-
expires: { type: 'string', description: 'Expiration date if set' },
134+
path_lower: {
135+
type: 'string',
136+
description: 'Lowercase path of the shared item',
137+
optional: true,
138+
},
139+
expires: { type: 'string', description: 'Expiration date if set', optional: true },
124140
link_permissions: {
125141
type: 'object',
126142
description: 'Permissions for the shared link',

apps/sim/tools/dropbox/delete.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const dropboxDeleteTool: ToolConfig<DropboxDeleteParams, DropboxDeleteRes
3434
}
3535
},
3636
body: (params) => ({
37-
path: params.path,
37+
path: params.path.trim(),
3838
}),
3939
},
4040

@@ -65,7 +65,7 @@ export const dropboxDeleteTool: ToolConfig<DropboxDeleteParams, DropboxDeleteRes
6565
properties: {
6666
'.tag': { type: 'string', description: 'Type: file, folder, or deleted' },
6767
name: { type: 'string', description: 'Name of the deleted item' },
68-
path_display: { type: 'string', description: 'Display path' },
68+
path_display: { type: 'string', description: 'Display path', optional: true },
6969
},
7070
},
7171
deleted: {

apps/sim/tools/dropbox/download.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const dropboxDownloadTool: ToolConfig<DropboxDownloadParams, DropboxDownl
3232
return {
3333
Authorization: `Bearer ${params.accessToken}`,
3434
'Content-Type': 'application/octet-stream',
35-
'Dropbox-API-Arg': httpHeaderSafeJson({ path: params.path }),
35+
'Dropbox-API-Arg': httpHeaderSafeJson({ path: params.path.trim() }),
3636
}
3737
},
3838
},
@@ -64,7 +64,7 @@ export const dropboxDownloadTool: ToolConfig<DropboxDownloadParams, DropboxDownl
6464
Authorization: `Bearer ${params.accessToken}`,
6565
'Content-Type': 'application/json',
6666
},
67-
body: JSON.stringify({ path: params.path }),
67+
body: JSON.stringify({ path: params.path.trim() }),
6868
})
6969
if (linkResponse.ok) {
7070
const linkData = await linkResponse.json()

apps/sim/tools/dropbox/get_metadata.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const dropboxGetMetadataTool: ToolConfig<
4949
}
5050
},
5151
body: (params) => ({
52-
path: params.path,
52+
path: params.path.trim(),
5353
include_media_info: params.includeMediaInfo ?? false,
5454
include_deleted: params.includeDeleted ?? false,
5555
}),
@@ -80,15 +80,27 @@ export const dropboxGetMetadataTool: ToolConfig<
8080
description: 'Metadata for the file or folder',
8181
properties: {
8282
'.tag': { type: 'string', description: 'Type: file, folder, or deleted' },
83-
id: { type: 'string', description: 'Unique identifier' },
83+
id: { type: 'string', description: 'Unique identifier', optional: true },
8484
name: { type: 'string', description: 'Name of the item' },
85-
path_display: { type: 'string', description: 'Display path' },
86-
path_lower: { type: 'string', description: 'Lowercase path' },
87-
size: { type: 'number', description: 'Size in bytes (files only)' },
88-
client_modified: { type: 'string', description: 'Client modification time' },
89-
server_modified: { type: 'string', description: 'Server modification time' },
90-
rev: { type: 'string', description: 'Revision identifier' },
91-
content_hash: { type: 'string', description: 'Content hash' },
85+
path_display: { type: 'string', description: 'Display path', optional: true },
86+
path_lower: { type: 'string', description: 'Lowercase path', optional: true },
87+
size: { type: 'number', description: 'Size in bytes (files only)', optional: true },
88+
client_modified: {
89+
type: 'string',
90+
description: 'Client modification time (files only)',
91+
optional: true,
92+
},
93+
server_modified: {
94+
type: 'string',
95+
description: 'Server modification time (files only)',
96+
optional: true,
97+
},
98+
rev: { type: 'string', description: 'Revision identifier (files only)', optional: true },
99+
content_hash: {
100+
type: 'string',
101+
description: 'Content hash (files only)',
102+
optional: true,
103+
},
92104
},
93105
},
94106
},

apps/sim/tools/dropbox/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { dropboxDeleteTool } from '@/tools/dropbox/delete'
55
import { dropboxDownloadTool } from '@/tools/dropbox/download'
66
import { dropboxGetMetadataTool } from '@/tools/dropbox/get_metadata'
77
import { dropboxListFolderTool } from '@/tools/dropbox/list_folder'
8+
import { dropboxListRevisionsTool } from '@/tools/dropbox/list_revisions'
9+
import { dropboxListSharedLinksTool } from '@/tools/dropbox/list_shared_links'
810
import { dropboxMoveTool } from '@/tools/dropbox/move'
11+
import { dropboxRestoreTool } from '@/tools/dropbox/restore'
912
import { dropboxSearchTool } from '@/tools/dropbox/search'
1013
import { dropboxUploadTool } from '@/tools/dropbox/upload'
1114

@@ -17,7 +20,10 @@ export {
1720
dropboxDownloadTool,
1821
dropboxGetMetadataTool,
1922
dropboxListFolderTool,
23+
dropboxListRevisionsTool,
24+
dropboxListSharedLinksTool,
2025
dropboxMoveTool,
26+
dropboxRestoreTool,
2127
dropboxSearchTool,
2228
dropboxUploadTool,
2329
}

apps/sim/tools/dropbox/list_folder.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const dropboxListFolderTool: ToolConfig<DropboxListFolderParams, DropboxL
5959
}
6060
},
6161
body: (params) => ({
62-
path: params.path === '/' ? '' : params.path,
62+
path: params.path.trim() === '/' ? '' : params.path.trim(),
6363
recursive: params.recursive ?? false,
6464
include_deleted: params.includeDeleted ?? false,
6565
include_media_info: params.includeMediaInfo ?? false,
@@ -96,10 +96,10 @@ export const dropboxListFolderTool: ToolConfig<DropboxListFolderParams, DropboxL
9696
type: 'object',
9797
properties: {
9898
'.tag': { type: 'string', description: 'Type: file, folder, or deleted' },
99-
id: { type: 'string', description: 'Unique identifier' },
99+
id: { type: 'string', description: 'Unique identifier', optional: true },
100100
name: { type: 'string', description: 'Name of the file/folder' },
101-
path_display: { type: 'string', description: 'Display path' },
102-
size: { type: 'number', description: 'Size in bytes (files only)' },
101+
path_display: { type: 'string', description: 'Display path', optional: true },
102+
size: { type: 'number', description: 'Size in bytes (files only)', optional: true },
103103
},
104104
},
105105
},

0 commit comments

Comments
 (0)