Skip to content

Commit a02c07f

Browse files
committed
fix(docusign): page envelope and template lists, ignore missing signing anchors, and guard envelope path ids
1 parent c1d1eea commit a02c07f

9 files changed

Lines changed: 163 additions & 11 deletions

File tree

‎apps/sim/blocks/blocks/docusign.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,15 @@ export const DocuSignBlock: BlockConfig = {
287287
id: 'count',
288288
title: 'Max Results',
289289
type: 'short-input',
290-
placeholder: '25',
290+
placeholder: '100',
291+
condition: { field: 'operation', value: ['list_envelopes', 'list_templates'] },
292+
mode: 'advanced',
293+
},
294+
{
295+
id: 'startPosition',
296+
title: 'Start Position',
297+
type: 'short-input',
298+
placeholder: '0',
291299
condition: { field: 'operation', value: ['list_envelopes', 'list_templates'] },
292300
mode: 'advanced',
293301
},
@@ -359,6 +367,7 @@ export const DocuSignBlock: BlockConfig = {
359367
toDate: { type: 'string', description: 'End date filter' },
360368
searchText: { type: 'string', description: 'Search text filter' },
361369
count: { type: 'string', description: 'Max results to return' },
370+
startPosition: { type: 'string', description: 'Zero-based index of the first result' },
362371
},
363372
outputs: {
364373
envelopeId: { type: 'string', description: 'Envelope ID' },
@@ -404,6 +413,18 @@ export const DocuSignBlock: BlockConfig = {
404413
fileName: { type: 'string', description: 'Document file name' },
405414
totalSetSize: { type: 'number', description: 'Total matching results' },
406415
resultSetSize: { type: 'number', description: 'Results returned in this response' },
416+
startPosition: {
417+
type: 'number',
418+
description: 'Zero-based index of the first result in this page',
419+
},
420+
endPosition: {
421+
type: 'number',
422+
description: 'Zero-based index of the last result in this page',
423+
},
424+
nextUri: {
425+
type: 'string',
426+
description: 'URI of the next page of results (null on the last page)',
427+
},
407428
},
408429
}
409430

‎apps/sim/lib/internal/docusign/execute-tool.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ const schemas = {
4545
envelopeStatus: z.string().optional(),
4646
searchText: z.string().optional(),
4747
count: z.string().optional(),
48+
startPosition: z.string().optional(),
4849
}),
4950
docusign_list_recipients: z.object({ ...auth, envelopeId: z.string().min(1) }),
5051
docusign_list_templates: z.object({
5152
...auth,
5253
searchText: z.string().optional(),
5354
count: z.string().optional(),
55+
startPosition: z.string().optional(),
5456
}),
5557
docusign_send_envelope: z.object({
5658
...auth,

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const { mockProcessFilesToUserFiles } = fileUtilsMockFns
4343
import { DocuSignOperationError } from '@/lib/internal/docusign/errors'
4444
import {
4545
executeDocuSignDownloadDocument,
46+
executeDocuSignGetEnvelope,
47+
executeDocuSignListEnvelopes,
48+
executeDocuSignListTemplates,
4649
executeDocuSignSendEnvelope,
4750
} from '@/lib/internal/docusign/operations'
4851

@@ -124,4 +127,43 @@ describe('DocuSign operations', () => {
124127
})
125128
expect(mockUploadCopilotFile).not.toHaveBeenCalled()
126129
})
130+
131+
it('sends a document that lacks the anchor strings instead of failing on missing anchors', async () => {
132+
await executeDocuSignSendEnvelope(
133+
{
134+
accessToken: 'access-token',
135+
emailSubject: 'Sign',
136+
signerEmail: 'a@example.com',
137+
signerName: 'A',
138+
file: { id: 'file-1' },
139+
},
140+
CONTEXT
141+
)
142+
143+
const body = JSON.parse(mocks.json.mock.calls[0][1].body)
144+
const tabs = body.recipients.signers[0].tabs
145+
expect(tabs.signHereTabs[0].anchorIgnoreIfNotPresent).toBe('true')
146+
expect(tabs.dateSignedTabs[0].anchorIgnoreIfNotPresent).toBe('true')
147+
})
148+
149+
it.each([
150+
['envelopes', executeDocuSignListEnvelopes],
151+
['templates', executeDocuSignListTemplates],
152+
] as const)('pages %s with start_position', async (_name, execute) => {
153+
await execute({ accessToken: 'access-token', count: '50', startPosition: '100' }, CONTEXT)
154+
155+
const query = new URL(`https://x${mocks.json.mock.calls[0][0]}`).searchParams
156+
expect(query.get('start_position')).toBe('100')
157+
expect(query.get('count')).toBe('50')
158+
})
159+
160+
it.each(['..', 'env-1/../../users'])(
161+
'rejects envelope id %s before contacting DocuSign',
162+
async (envelopeId) => {
163+
await expect(
164+
executeDocuSignGetEnvelope({ accessToken: 'access-token', envelopeId }, CONTEXT)
165+
).rejects.toMatchObject({ status: 400 })
166+
expect(mocks.create).not.toHaveBeenCalled()
167+
}
168+
)
127169
})

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
DocuSignSendEnvelopeParams,
2121
DocuSignVoidEnvelopeParams,
2222
} from '@/tools/docusign/types'
23+
import { safeUrlPathSegment } from '@/tools/url-path'
2324

2425
const logger = createLogger('DocuSignOperations')
2526
const MAX_LEGACY_INLINE_DOCUMENT_BYTES = 7 * 1024 * 1024
@@ -37,6 +38,14 @@ function jsonBody(data: Record<string, unknown>): RequestInit {
3738
return { method: 'POST', body: JSON.stringify(data) }
3839
}
3940

41+
function pathSegment(value: string, paramName: string): string {
42+
try {
43+
return safeUrlPathSegment(value, paramName)
44+
} catch (error) {
45+
throw new DocuSignOperationError(getErrorMessage(error, `Invalid ${paramName}`), 400)
46+
}
47+
}
48+
4049
async function client(
4150
input: { accessToken: string; instanceUrl?: string },
4251
signal?: AbortSignal
@@ -141,6 +150,7 @@ export async function executeDocuSignSendEnvelope(
141150
anchorUnits: 'pixels',
142151
anchorXOffset: '0',
143152
anchorYOffset: '0',
153+
anchorIgnoreIfNotPresent: 'true',
144154
},
145155
],
146156
dateSignedTabs: [
@@ -149,6 +159,7 @@ export async function executeDocuSignSendEnvelope(
149159
anchorUnits: 'pixels',
150160
anchorXOffset: '0',
151161
anchorYOffset: '0',
162+
anchorIgnoreIfNotPresent: 'true',
152163
},
153164
],
154165
},
@@ -191,9 +202,10 @@ export async function executeDocuSignGetEnvelope(
191202
input: DocuSignGetEnvelopeParams,
192203
context: DocuSignOperationContext
193204
) {
205+
const envelopeId = pathSegment(input.envelopeId, 'envelopeId')
194206
const provider = await client(input, context.signal)
195207
return provider.json(
196-
`/envelopes/${input.envelopeId.trim()}?include=recipients,documents`,
208+
`/envelopes/${envelopeId}?include=recipients,documents`,
197209
{},
198210
'DocuSign envelope response',
199211
'Failed to get envelope',
@@ -216,6 +228,7 @@ export async function executeDocuSignListEnvelopes(
216228
if (input.envelopeStatus) query.append('status', input.envelopeStatus)
217229
if (input.searchText) query.append('search_text', input.searchText)
218230
if (input.count) query.append('count', input.count)
231+
if (input.startPosition) query.append('start_position', input.startPosition)
219232
const provider = await client(input, context.signal)
220233
return provider.json(
221234
`/envelopes?${query}`,
@@ -230,9 +243,10 @@ export async function executeDocuSignVoidEnvelope(
230243
input: DocuSignVoidEnvelopeParams,
231244
context: DocuSignOperationContext
232245
) {
246+
const envelopeId = pathSegment(input.envelopeId, 'envelopeId')
233247
const provider = await client(input, context.signal)
234248
await provider.json(
235-
`/envelopes/${input.envelopeId.trim()}`,
249+
`/envelopes/${envelopeId}`,
236250
{ method: 'PUT', body: JSON.stringify({ status: 'voided', voidedReason: input.voidedReason }) },
237251
'DocuSign void envelope response',
238252
'Failed to void envelope',
@@ -245,10 +259,11 @@ export async function executeDocuSignDownloadDocument(
245259
input: DocuSignDownloadDocumentParams,
246260
context: DocuSignOperationContext
247261
) {
248-
const documentId = input.documentId || 'combined'
262+
const envelopeId = pathSegment(input.envelopeId, 'envelopeId')
263+
const documentId = pathSegment(input.documentId || 'combined', 'documentId')
249264
const provider = await client(input, context.signal)
250265
const { buffer, contentType, fileName } = await provider.document(
251-
input.envelopeId.trim(),
266+
envelopeId,
252267
documentId,
253268
context.signal
254269
)
@@ -281,6 +296,7 @@ export async function executeDocuSignListTemplates(
281296
const query = new URLSearchParams()
282297
if (input.searchText) query.append('search_text', input.searchText)
283298
if (input.count) query.append('count', input.count)
299+
if (input.startPosition) query.append('start_position', input.startPosition)
284300
const suffix = query.size ? `?${query}` : ''
285301
const provider = await client(input, context.signal)
286302
return provider.json(
@@ -296,9 +312,10 @@ export async function executeDocuSignListRecipients(
296312
input: DocuSignListRecipientsParams,
297313
context: DocuSignOperationContext
298314
) {
315+
const envelopeId = pathSegment(input.envelopeId, 'envelopeId')
299316
const provider = await client(input, context.signal)
300317
return provider.json(
301-
`/envelopes/${input.envelopeId.trim()}/recipients`,
318+
`/envelopes/${envelopeId}/recipients`,
302319
{},
303320
'DocuSign recipients response',
304321
'Failed to list recipients',

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type {
22
DocuSignListEnvelopesParams,
33
DocuSignListEnvelopesResponse,
44
} from '@/tools/docusign/types'
5-
import { ENVELOPES_ARRAY_OUTPUT } from '@/tools/docusign/types'
5+
import { ENVELOPES_ARRAY_OUTPUT, LIST_PAGINATION_OUTPUTS } from '@/tools/docusign/types'
6+
import { listPagination } from '@/tools/docusign/utils'
67
import type { InternalToolConfig } from '@/tools/types'
78

89
export const docusignListEnvelopesTool: InternalToolConfig<
@@ -61,7 +62,14 @@ export const docusignListEnvelopesTool: InternalToolConfig<
6162
type: 'string',
6263
required: false,
6364
visibility: 'user-or-llm',
64-
description: 'Maximum number of envelopes to return (default: 25)',
65+
description: 'Maximum number of envelopes to return (max 1000)',
66+
},
67+
startPosition: {
68+
type: 'string',
69+
required: false,
70+
visibility: 'user-or-llm',
71+
description:
72+
'Zero-based index of the first result to return, for paging through envelopes (default: 0)',
6573
},
6674
},
6775

@@ -74,6 +82,7 @@ export const docusignListEnvelopesTool: InternalToolConfig<
7482
envelopeStatus: params.envelopeStatus,
7583
searchText: params.searchText,
7684
count: params.count,
85+
startPosition: params.startPosition,
7786
}),
7887
},
7988

@@ -97,6 +106,7 @@ export const docusignListEnvelopesTool: InternalToolConfig<
97106
envelopes,
98107
totalSetSize: Number(data.totalSetSize) || 0,
99108
resultSetSize: Number(data.resultSetSize) || envelopes.length,
109+
...listPagination(data),
100110
},
101111
}
102112
},
@@ -105,5 +115,6 @@ export const docusignListEnvelopesTool: InternalToolConfig<
105115
envelopes: ENVELOPES_ARRAY_OUTPUT,
106116
totalSetSize: { type: 'number', description: 'Total number of matching envelopes' },
107117
resultSetSize: { type: 'number', description: 'Number of envelopes returned in this response' },
118+
...LIST_PAGINATION_OUTPUTS,
108119
},
109120
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type {
22
DocuSignListTemplatesParams,
33
DocuSignListTemplatesResponse,
44
} from '@/tools/docusign/types'
5-
import { TEMPLATES_ARRAY_OUTPUT } from '@/tools/docusign/types'
5+
import { LIST_PAGINATION_OUTPUTS, TEMPLATES_ARRAY_OUTPUT } from '@/tools/docusign/types'
6+
import { listPagination } from '@/tools/docusign/utils'
67
import type { InternalToolConfig } from '@/tools/types'
78

89
export const docusignListTemplatesTool: InternalToolConfig<
@@ -43,7 +44,15 @@ export const docusignListTemplatesTool: InternalToolConfig<
4344
type: 'string',
4445
required: false,
4546
visibility: 'user-or-llm',
46-
description: 'Maximum number of templates to return',
47+
description:
48+
'Maximum number of templates to return (DocuSign returns up to 2000 when omitted)',
49+
},
50+
startPosition: {
51+
type: 'string',
52+
required: false,
53+
visibility: 'user-or-llm',
54+
description:
55+
'Zero-based index of the first result to return, for paging through templates (default: 0)',
4756
},
4857
},
4958

@@ -53,6 +62,7 @@ export const docusignListTemplatesTool: InternalToolConfig<
5362
instanceUrl: params.instanceUrl,
5463
searchText: params.searchText,
5564
count: params.count,
65+
startPosition: params.startPosition,
5666
}),
5767
},
5868

@@ -75,6 +85,7 @@ export const docusignListTemplatesTool: InternalToolConfig<
7585
templates,
7686
totalSetSize: Number(data.totalSetSize) || 0,
7787
resultSetSize: Number(data.resultSetSize) || templates.length,
88+
...listPagination(data),
7889
},
7990
}
8091
},
@@ -83,5 +94,6 @@ export const docusignListTemplatesTool: InternalToolConfig<
8394
templates: TEMPLATES_ARRAY_OUTPUT,
8495
totalSetSize: { type: 'number', description: 'Total number of matching templates' },
8596
resultSetSize: { type: 'number', description: 'Number of templates returned in this response' },
97+
...LIST_PAGINATION_OUTPUTS,
8698
},
8799
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export const docusignSendEnvelopeTool: InternalToolConfig<
7272
type: 'file',
7373
required: false,
7474
visibility: 'user-or-llm',
75-
description: 'Document file to send for signature',
75+
description:
76+
'Document file to send for signature. Text "/sig1/" and "/date1/" in the document anchor the signature and date fields; without them the signer places fields freely',
7677
},
7778
status: {
7879
type: 'string',

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,25 @@ export const TEMPLATE_OUTPUT_PROPERTIES = {
5252
lastModified: { type: 'string', description: 'ISO 8601 last modified date' },
5353
} as const satisfies Record<string, OutputProperty>
5454

55+
/** Pagination outputs shared by the envelope and template list tools */
56+
export const LIST_PAGINATION_OUTPUTS = {
57+
startPosition: {
58+
type: 'number',
59+
description: 'Zero-based index of the first result in this page',
60+
optional: true,
61+
},
62+
endPosition: {
63+
type: 'number',
64+
description: 'Zero-based index of the last result in this page',
65+
optional: true,
66+
},
67+
nextUri: {
68+
type: 'string',
69+
description: 'URI of the next page of results; null on the last page',
70+
optional: true,
71+
},
72+
} as const satisfies Record<string, OutputProperty>
73+
5574
export const ENVELOPES_ARRAY_OUTPUT: OutputProperty = {
5675
type: 'array',
5776
description: 'Array of DocuSign envelopes',
@@ -117,6 +136,7 @@ export interface DocuSignListEnvelopesParams {
117136
envelopeStatus?: string
118137
searchText?: string
119138
count?: string
139+
startPosition?: string
120140
}
121141

122142
export interface DocuSignVoidEnvelopeParams {
@@ -138,6 +158,7 @@ export interface DocuSignListTemplatesParams {
138158
instanceUrl?: string
139159
searchText?: string
140160
count?: string
161+
startPosition?: string
141162
}
142163

143164
export interface DocuSignListRecipientsParams {
@@ -193,6 +214,9 @@ export interface DocuSignListEnvelopesResponse extends ToolResponse {
193214
}>
194215
totalSetSize: number
195216
resultSetSize: number
217+
startPosition: number | null
218+
endPosition: number | null
219+
nextUri: string | null
196220
}
197221
}
198222

@@ -224,6 +248,9 @@ export interface DocuSignListTemplatesResponse extends ToolResponse {
224248
}>
225249
totalSetSize: number
226250
resultSetSize: number
251+
startPosition: number | null
252+
endPosition: number | null
253+
nextUri: string | null
227254
}
228255
}
229256

0 commit comments

Comments
 (0)