Skip to content

Commit b7665e8

Browse files
zendesk v2 with paginated output
1 parent 87a1c62 commit b7665e8

File tree

9 files changed

+223
-36
lines changed

9 files changed

+223
-36
lines changed

apps/sim/blocks/blocks/zendesk.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ Return ONLY the search query - no explanations.`,
533533
tools: {
534534
access: [
535535
'zendesk_get_tickets',
536+
'zendesk_get_tickets_v2',
536537
'zendesk_get_ticket',
537538
'zendesk_create_ticket',
538539
'zendesk_create_tickets_bulk',
@@ -563,7 +564,7 @@ Return ONLY the search query - no explanations.`,
563564
tool: (params) => {
564565
switch (params.operation) {
565566
case 'get_tickets':
566-
return 'zendesk_get_tickets'
567+
return 'zendesk_get_tickets_v2'
567568
case 'get_ticket':
568569
return 'zendesk_get_ticket'
569570
case 'create_ticket':

apps/sim/lib/paginated-cache/paginate.test.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ describe('autoPaginate', () => {
126126
expect(result.output.tickets).toEqual(
127127
expect.objectContaining({ _type: 'paginated_cache_ref', totalPages: 3, totalItems: 3 })
128128
)
129-
expect(result.output.paging).toEqual({ has_more: false, after_cursor: null })
130-
expect(result.output.metadata).toEqual({ total_returned: 1, has_more: false })
129+
expect(result.output.paging).toBeUndefined()
130+
expect(result.output.metadata).toBeUndefined()
131+
expect(Object.keys(result.output)).toEqual(['tickets'])
131132
})
132133

133134
it('respects maxPages', async () => {
@@ -235,10 +236,7 @@ describe('autoPaginate', () => {
235236
})
236237

237238
const outputKeys = Object.keys(result.output)
238-
expect(outputKeys).toContain('items')
239-
expect(outputKeys).toContain('cursor')
240-
expect(outputKeys).not.toContain('metadata')
241-
expect(outputKeys).not.toContain('paging')
239+
expect(outputKeys).toEqual(['items'])
242240
})
243241
})
244242

apps/sim/lib/paginated-cache/paginate.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ export async function autoPaginate(options: AutoPaginateOptions): Promise<ToolRe
5050

5151
let totalItems = 0
5252
let pageIndex = 0
53-
let lastOutput = initialResult.output
5453

5554
const initialItems = config.getItems(initialResult.output)
5655
await cache.storePage(cacheId, pageIndex, initialItems)
@@ -71,7 +70,6 @@ export async function autoPaginate(options: AutoPaginateOptions): Promise<ToolRe
7170
const pageItems = config.getItems(pageResult.output)
7271
await cache.storePage(cacheId, pageIndex, pageItems)
7372
totalItems += pageItems.length
74-
lastOutput = pageResult.output
7573
pageIndex++
7674

7775
nextToken = config.getNextPageToken(pageResult.output)
@@ -94,7 +92,6 @@ export async function autoPaginate(options: AutoPaginateOptions): Promise<ToolRe
9492
return {
9593
...initialResult,
9694
output: {
97-
...lastOutput,
9895
[config.pageField]: reference,
9996
},
10097
}

apps/sim/lib/paginated-cache/redis-cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const logger = createLogger('RedisPaginatedCache')
99
const REDIS_KEY_PREFIX = 'pagcache:'
1010

1111
/** Safety-net TTL: 2× the max async execution timeout. Explicit cleanup is the primary mechanism. */
12-
const DEFAULT_TTL_MS = Number(env.EXECUTION_TIMEOUT_ASYNC_ENTERPRISE) * 1000 * 2
12+
const DEFAULT_TTL_MS = (Number(env.EXECUTION_TIMEOUT_ASYNC_ENTERPRISE) || 5400) * 1000 * 2
1313

1414
export class RedisPaginatedCache implements PaginatedCacheStorageAdapter {
1515
constructor(

apps/sim/tools/index.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -620,10 +620,6 @@ async function maybeAutoPaginate(
620620
) {
621621
return finalResult
622622
}
623-
const nextToken = tool.pagination.getNextPageToken(finalResult.output)
624-
if (nextToken === null) {
625-
return finalResult
626-
}
627623
return autoPaginate({
628624
initialResult: finalResult,
629625
params: contextParams,

apps/sim/tools/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2669,6 +2669,7 @@ import {
26692669
zendeskGetOrganizationsTool,
26702670
zendeskGetOrganizationTool,
26712671
zendeskGetTicketsTool,
2672+
zendeskGetTicketsV2Tool,
26722673
zendeskGetTicketTool,
26732674
zendeskGetUsersTool,
26742675
zendeskGetUserTool,
@@ -5016,6 +5017,7 @@ export const tools: Record<string, ToolConfig> = {
50165017
mailchimp_create_batch_operation: mailchimpCreateBatchOperationTool,
50175018
mailchimp_delete_batch_operation: mailchimpDeleteBatchOperationTool,
50185019
zendesk_get_tickets: zendeskGetTicketsTool,
5020+
zendesk_get_tickets_v2: zendeskGetTicketsV2Tool,
50195021
zendesk_get_ticket: zendeskGetTicketTool,
50205022
zendesk_create_ticket: zendeskCreateTicketTool,
50215023
zendesk_create_tickets_bulk: zendeskCreateTicketsBulkTool,

apps/sim/tools/zendesk/get_tickets.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ToolConfig } from '@/tools/types'
22
import {
3+
appendCursorPaginationParams,
34
buildZendeskUrl,
45
extractCursorPagingInfo,
56
handleZendeskError,
@@ -18,7 +19,7 @@ export interface ZendeskGetTicketsParams {
1819
assigneeId?: string
1920
organizationId?: string
2021
sort?: string
21-
/** Internal: set by auto-pagination via buildNextPageParams */
22+
perPage?: string
2223
pageAfter?: string
2324
}
2425

@@ -43,7 +44,7 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
4344
id: 'zendesk_get_tickets',
4445
name: 'Get Tickets from Zendesk',
4546
description: 'Retrieve a list of tickets from Zendesk with optional filtering',
46-
version: '2.0.0',
47+
version: '1.0.0',
4748

4849
params: {
4950
email: {
@@ -101,6 +102,18 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
101102
description:
102103
'Sort field for ticket listing (only applies without filters): "updated_at", "id", or "status". Prefix with "-" for descending (e.g., "-updated_at")',
103104
},
105+
perPage: {
106+
type: 'string',
107+
required: false,
108+
visibility: 'user-or-llm',
109+
description: 'Results per page as a number string (default: "100", max: "100")',
110+
},
111+
pageAfter: {
112+
type: 'string',
113+
required: false,
114+
visibility: 'user-or-llm',
115+
description: 'Cursor from a previous response to fetch the next page of results',
116+
},
104117
},
105118

106119
request: {
@@ -125,16 +138,15 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
125138
const queryParams = new URLSearchParams()
126139
queryParams.append('query', searchTerms.join(' '))
127140
queryParams.append('filter[type]', 'ticket')
128-
queryParams.append('page[size]', '100')
129-
if (params.pageAfter) queryParams.append('page[after]', params.pageAfter)
141+
appendCursorPaginationParams(queryParams, params)
130142

131143
return `${buildZendeskUrl(params.subdomain, '/search/export')}?${queryParams.toString()}`
132144
}
133145

146+
// No filters - use the simple /tickets endpoint with cursor-based pagination
134147
const queryParams = new URLSearchParams()
135148
if (params.sort) queryParams.append('sort', params.sort)
136-
queryParams.append('page[size]', '100')
137-
if (params.pageAfter) queryParams.append('page[after]', params.pageAfter)
149+
appendCursorPaginationParams(queryParams, params)
138150

139151
const query = queryParams.toString()
140152
const url = buildZendeskUrl(params.subdomain, '/tickets')
@@ -182,19 +194,4 @@ export const zendeskGetTicketsTool: ToolConfig<ZendeskGetTicketsParams, ZendeskG
182194
paging: PAGING_OUTPUT,
183195
metadata: METADATA_OUTPUT,
184196
},
185-
186-
pagination: {
187-
pageField: 'tickets',
188-
getItems: (output) => output.tickets ?? [],
189-
getNextPageToken: (output) => {
190-
if (output.paging?.has_more && output.paging?.after_cursor) {
191-
return output.paging.after_cursor
192-
}
193-
return null
194-
},
195-
buildNextPageParams: (params, token) => ({
196-
...params,
197-
pageAfter: String(token),
198-
}),
199-
},
200197
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import type { ToolConfig } from '@/tools/types'
2+
import {
3+
buildZendeskUrl,
4+
extractCursorPagingInfo,
5+
handleZendeskError,
6+
TICKETS_ARRAY_OUTPUT,
7+
} from '@/tools/zendesk/types'
8+
9+
export interface ZendeskGetTicketsV2Params {
10+
email: string
11+
apiToken: string
12+
subdomain: string
13+
status?: string
14+
priority?: string
15+
type?: string
16+
assigneeId?: string
17+
organizationId?: string
18+
sort?: string
19+
/** Internal: set by auto-pagination via buildNextPageParams */
20+
pageAfter?: string
21+
}
22+
23+
export interface ZendeskGetTicketsV2Response {
24+
success: boolean
25+
output: {
26+
tickets: any[]
27+
paging?: {
28+
after_cursor: string | null
29+
has_more: boolean
30+
}
31+
metadata: {
32+
total_returned: number
33+
has_more: boolean
34+
}
35+
success: boolean
36+
}
37+
}
38+
39+
export const zendeskGetTicketsV2Tool: ToolConfig<
40+
ZendeskGetTicketsV2Params,
41+
ZendeskGetTicketsV2Response
42+
> = {
43+
id: 'zendesk_get_tickets_v2',
44+
name: 'Get All Tickets from Zendesk',
45+
description:
46+
'Retrieve all tickets from Zendesk with optional filtering. Automatically paginates through all results.',
47+
version: '2.0.0',
48+
49+
params: {
50+
email: {
51+
type: 'string',
52+
required: true,
53+
visibility: 'user-only',
54+
description: 'Your Zendesk email address',
55+
},
56+
apiToken: {
57+
type: 'string',
58+
required: true,
59+
visibility: 'hidden',
60+
description: 'Zendesk API token',
61+
},
62+
subdomain: {
63+
type: 'string',
64+
required: true,
65+
visibility: 'user-only',
66+
description: 'Your Zendesk subdomain (e.g., "mycompany" for mycompany.zendesk.com)',
67+
},
68+
status: {
69+
type: 'string',
70+
required: false,
71+
visibility: 'user-or-llm',
72+
description: 'Filter by status: "new", "open", "pending", "hold", "solved", or "closed"',
73+
},
74+
priority: {
75+
type: 'string',
76+
required: false,
77+
visibility: 'user-or-llm',
78+
description: 'Filter by priority: "low", "normal", "high", or "urgent"',
79+
},
80+
type: {
81+
type: 'string',
82+
required: false,
83+
visibility: 'user-or-llm',
84+
description: 'Filter by type: "problem", "incident", "question", or "task"',
85+
},
86+
assigneeId: {
87+
type: 'string',
88+
required: false,
89+
visibility: 'user-or-llm',
90+
description: 'Filter by assignee user ID as a numeric string (e.g., "12345")',
91+
},
92+
organizationId: {
93+
type: 'string',
94+
required: false,
95+
visibility: 'user-or-llm',
96+
description: 'Filter by organization ID as a numeric string (e.g., "67890")',
97+
},
98+
sort: {
99+
type: 'string',
100+
required: false,
101+
visibility: 'user-or-llm',
102+
description:
103+
'Sort field for ticket listing (only applies without filters): "updated_at", "id", or "status". Prefix with "-" for descending (e.g., "-updated_at")',
104+
},
105+
},
106+
107+
request: {
108+
url: (params) => {
109+
const hasFilters =
110+
params.status ||
111+
params.priority ||
112+
params.type ||
113+
params.assigneeId ||
114+
params.organizationId
115+
116+
if (hasFilters) {
117+
const searchTerms: string[] = ['type:ticket']
118+
if (params.status) searchTerms.push(`status:${params.status}`)
119+
if (params.priority) searchTerms.push(`priority:${params.priority}`)
120+
if (params.type) searchTerms.push(`ticket_type:${params.type}`)
121+
if (params.assigneeId) searchTerms.push(`assignee_id:${params.assigneeId}`)
122+
if (params.organizationId) searchTerms.push(`organization_id:${params.organizationId}`)
123+
124+
const queryParams = new URLSearchParams()
125+
queryParams.append('query', searchTerms.join(' '))
126+
queryParams.append('filter[type]', 'ticket')
127+
queryParams.append('page[size]', '100')
128+
if (params.pageAfter) queryParams.append('page[after]', params.pageAfter)
129+
130+
return `${buildZendeskUrl(params.subdomain, '/search/export')}?${queryParams.toString()}`
131+
}
132+
133+
const queryParams = new URLSearchParams()
134+
if (params.sort) queryParams.append('sort', params.sort)
135+
queryParams.append('page[size]', '100')
136+
if (params.pageAfter) queryParams.append('page[after]', params.pageAfter)
137+
138+
const query = queryParams.toString()
139+
const url = buildZendeskUrl(params.subdomain, '/tickets')
140+
return query ? `${url}?${query}` : url
141+
},
142+
method: 'GET',
143+
headers: (params) => {
144+
const credentials = `${params.email}/token:${params.apiToken}`
145+
const base64Credentials = Buffer.from(credentials).toString('base64')
146+
return {
147+
Authorization: `Basic ${base64Credentials}`,
148+
'Content-Type': 'application/json',
149+
}
150+
},
151+
},
152+
153+
transformResponse: async (response: Response) => {
154+
if (!response.ok) {
155+
const data = await response.json()
156+
handleZendeskError(data, response.status, 'get_tickets')
157+
}
158+
159+
const data = await response.json()
160+
const tickets = data.tickets || data.results || []
161+
const paging = extractCursorPagingInfo(data)
162+
163+
return {
164+
success: true,
165+
output: {
166+
tickets,
167+
paging,
168+
metadata: {
169+
total_returned: tickets.length,
170+
has_more: paging.has_more,
171+
},
172+
success: true,
173+
},
174+
}
175+
},
176+
177+
outputs: {
178+
tickets: TICKETS_ARRAY_OUTPUT,
179+
},
180+
181+
pagination: {
182+
pageField: 'tickets',
183+
getItems: (output) => output.tickets ?? [],
184+
getNextPageToken: (output) => {
185+
if (output.paging?.has_more && output.paging?.after_cursor) {
186+
return output.paging.after_cursor
187+
}
188+
return null
189+
},
190+
buildNextPageParams: (params, token) => ({
191+
...params,
192+
pageAfter: String(token),
193+
}),
194+
},
195+
}

apps/sim/tools/zendesk/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export { zendeskGetOrganizationTool } from './get_organization'
1313
export { zendeskGetOrganizationsTool } from './get_organizations'
1414
export { zendeskGetTicketTool } from './get_ticket'
1515
export { zendeskGetTicketsTool } from './get_tickets'
16+
export { zendeskGetTicketsV2Tool } from './get_tickets_v2'
1617
export { zendeskGetUserTool } from './get_user'
1718
export { zendeskGetUsersTool } from './get_users'
1819
export { zendeskMergeTicketsTool } from './merge_tickets'

0 commit comments

Comments
 (0)