Skip to content

Commit 8f985ec

Browse files
committed
fix(integrations): fail loudly on partial Notion reads, validate Asana limits and assignee, accept cursor-only Dropbox paging, normalize Supabase order modifiers, and mark nullable pagination outputs
1 parent b200178 commit 8f985ec

21 files changed

Lines changed: 163 additions & 33 deletions

File tree

‎apps/docs/content/docs/integrations/box.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ List files and folders in a Box folder
156156
| ↳ `size` | number | Item size in bytes |
157157
| ↳ `createdAt` | string | Creation timestamp |
158158
| ↳ `modifiedAt` | string | Last modified timestamp |
159-
| `totalCount` | number | Total number of items in the folder \(omitted by Box for marker pagination\) |
159+
| `totalCount` | number | Total number of items in the folder; null with marker pagination, where Box omits it |
160160
| `offset` | number | Current pagination offset |
161161
| `limit` | number | Current pagination limit |
162162
| `nextMarker` | string | Marker for the next page when using marker-based pagination |

‎apps/docs/content/docs/integrations/dropbox.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ List the contents of a folder in Dropbox
104104

105105
| Parameter | Type | Required | Description |
106106
| --------- | ---- | -------- | ----------- |
107-
| `path` | string | Yes | The path of the folder to list \(use "" for root\) |
107+
| `path` | string | No | The path of the folder to list \(use "" for root\). Required unless cursor is set |
108108
| `recursive` | boolean | No | If true, list contents recursively |
109109
| `includeDeleted` | boolean | No | If true, include deleted files/folders |
110110
| `includeMediaInfo` | boolean | No | If true, include media info for photos/videos |
@@ -322,7 +322,7 @@ Search for files and folders in Dropbox
322322

323323
| Parameter | Type | Required | Description |
324324
| --------- | ---- | -------- | ----------- |
325-
| `query` | string | Yes | The search query |
325+
| `query` | string | No | The search query. Required unless cursor is set |
326326
| `path` | string | No | Dropbox folder path to limit search scope \(e.g., /folder/subfolder\) |
327327
| `fileExtensions` | string | No | Comma-separated list of file extensions to filter by \(e.g., pdf,xlsx\) |
328328
| `maxResults` | number | No | Maximum number of results to return \(default: 100\) |

‎apps/sim/lib/internal/asana/operations.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ describe('Asana operations', () => {
8181
expect(parsedUrl.searchParams.get('offset')).toBe('tok')
8282
})
8383

84+
it('rejects a workspace task list without an assignee and out-of-range page sizes', async () => {
85+
await expect(executeAsanaGetTask({ ...AUTH, workspace: 'workspace1' })).rejects.toMatchObject({
86+
status: 400,
87+
body: { error: 'assignee is required when listing tasks by workspace' },
88+
})
89+
await expect(
90+
executeAsanaSearchTasks({ ...AUTH, workspace: 'workspace1', limit: 500 })
91+
).rejects.toMatchObject({ status: 400 })
92+
await expect(
93+
executeAsanaGetTask({ ...AUTH, project: 'project1', limit: '2.5' })
94+
).rejects.toMatchObject({ status: 400 })
95+
expect(fetchMock).not.toHaveBeenCalled()
96+
})
97+
8498
it('sends the search page size', async () => {
8599
fetchMock.mockResolvedValueOnce(new Response(JSON.stringify({ data: [] })))
86100
await executeAsanaSearchTasks({ ...AUTH, workspace: 'workspace1', limit: 25 })

‎apps/sim/lib/internal/asana/operations.ts‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ function validateId(value: string, name: string): void {
3535
}
3636
}
3737

38+
/** Asana accepts page sizes from 1 to 100; reject anything else before calling the API. */
39+
function pageLimit(value: string | number): string {
40+
const limit = Number(value)
41+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
42+
const error = 'limit must be a whole number from 1 to 100'
43+
throw new AsanaOperationError(error, 400, { error })
44+
}
45+
return String(limit)
46+
}
47+
3848
function dataObject(result: AsanaJsonObject): AsanaJsonObject {
3949
return toRecord(result.data)
4050
}
@@ -289,10 +299,14 @@ export async function executeAsanaGetTask(input: AsanaGetTaskBody, signal?: Abor
289299
params.append('project', input.project)
290300
} else if (input.workspace) {
291301
validateId(input.workspace, 'workspace')
302+
if (!input.assignee) {
303+
const error = 'assignee is required when listing tasks by workspace'
304+
throw new AsanaOperationError(error, 400, { error })
305+
}
292306
params.append('workspace', input.workspace)
293-
if (input.assignee) params.append('assignee', input.assignee)
307+
params.append('assignee', input.assignee)
294308
}
295-
params.append('limit', input.limit ? String(input.limit) : '50')
309+
params.append('limit', input.limit ? pageLimit(input.limit) : '50')
296310
if (input.offset) params.append('offset', input.offset)
297311
params.append('opt_fields', TASK_OPT_FIELDS)
298312
const result = await client.json(`/tasks?${params.toString()}`, { method: 'GET' }, signal)
@@ -357,7 +371,7 @@ export async function executeAsanaSearchTasks(input: AsanaSearchTasksBody, signa
357371
if (input.completed !== undefined) {
358372
params.append('completed', String(input.completed))
359373
}
360-
if (input.limit) params.append('limit', String(input.limit))
374+
if (input.limit) params.append('limit', pageLimit(input.limit))
361375
params.append('opt_fields', TASK_OPT_FIELDS)
362376
const result = await new AsanaClient(input.accessToken).json(
363377
`/workspaces/${input.workspace}/tasks/search?${params.toString()}`,

‎apps/sim/tools/airtable/types.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ interface AirtableRecord {
1010
interface AirtableBase {
1111
id: string
1212
name: string
13-
permissionLevel: 'none' | 'read' | 'comment' | 'edit' | 'create'
13+
permissionLevel:
14+
| 'none'
15+
| 'read'
16+
| 'comment'
17+
| 'edit'
18+
| 'create'
19+
| 'interfaceOnly'
20+
| 'permissionSetOnly'
1421
}
1522

1623
interface AirtableFieldOption {

‎apps/sim/tools/asana/get_task.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ export const asanaGetTaskTool: InternalToolConfig<AsanaGetTaskParams, AsanaGetTa
134134
},
135135
next_page: {
136136
type: 'object',
137+
nullable: true,
137138
description: 'Pagination info when fetching multiple tasks; null on the last page',
138139
optional: true,
139140
properties: {

‎apps/sim/tools/box/list_folder_items.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export const boxListFolderItemsTool: ToolConfig<BoxListFolderItemsParams, BoxFol
106106
createdAt: item.created_at ?? null,
107107
modifiedAt: item.modified_at ?? null,
108108
})),
109-
totalCount: data.total_count ?? 0,
109+
totalCount: data.total_count ?? null,
110110
offset: data.offset ?? 0,
111111
limit: data.limit ?? 0,
112112
nextMarker: data.next_marker ?? null,

‎apps/sim/tools/box/types.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export interface BoxFileInfoResponse extends ToolResponse {
127127
export interface BoxFolderItemsResponse extends ToolResponse {
128128
output: {
129129
entries: Array<Record<string, unknown>>
130-
totalCount: number
130+
totalCount: number | null
131131
offset: number
132132
limit: number
133133
nextMarker: string | null
@@ -241,7 +241,9 @@ export const FOLDER_ITEMS_OUTPUT_PROPERTIES = {
241241
},
242242
totalCount: {
243243
type: 'number',
244-
description: 'Total number of items in the folder (omitted by Box for marker pagination)',
244+
description:
245+
'Total number of items in the folder; null with marker pagination, where Box omits it',
246+
nullable: true,
245247
},
246248
offset: { type: 'number', description: 'Current pagination offset' },
247249
limit: { type: 'number', description: 'Current pagination limit' },

‎apps/sim/tools/clickup/get_tasks.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ export const clickupGetTasksTool: ToolConfig<ClickUpGetTasksParams, ClickUpTaskL
229229
type: 'boolean',
230230
description: 'Whether this is the last page of results',
231231
optional: true,
232+
nullable: true,
232233
},
233234
},
234235
}

‎apps/sim/tools/docusign/types.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,19 @@ export const LIST_PAGINATION_OUTPUTS = {
5858
type: 'number',
5959
description: 'Zero-based index of the first result in this page',
6060
optional: true,
61+
nullable: true,
6162
},
6263
endPosition: {
6364
type: 'number',
6465
description: 'Zero-based index of the last result in this page',
6566
optional: true,
67+
nullable: true,
6668
},
6769
nextUri: {
6870
type: 'string',
6971
description: 'URI of the next page of results; null on the last page',
7072
optional: true,
73+
nullable: true,
7174
},
7275
} as const satisfies Record<string, OutputProperty>
7376

0 commit comments

Comments
 (0)