feat(docusign): add docusign integration (#3661)

* feat(docusign): add DocuSign e-signature integration

* fix(docusign): add base_uri null check and move file normalization to params

* fix(docusign): use canonical param documentFile instead of raw subBlock IDs

* fix(docusign): validate document file is present before sending envelope

* fix(docusign): rename tool files from kebab-case to snake_case for docs generation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-03-18 22:07:51 -07:00
committed by GitHub
parent 5f7a980c5f
commit 638063cac1
24 changed files with 2222 additions and 0 deletions

View File

@@ -1146,6 +1146,25 @@ export function DevinIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function DocuSignIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 1547 1549' xmlns='http://www.w3.org/2000/svg'>
<path
d='m1113.4 1114.9v395.6c0 20.8-16.7 37.6-37.5 37.6h-1038.4c-20.7 0-37.5-16.8-37.5-37.6v-1039c0-20.7 16.8-37.5 37.5-37.5h394.3v643.4c0 20.7 16.8 37.5 37.5 37.5z'
fill='#4c00ff'
/>
<path
d='m1546 557.1c0 332.4-193.9 557-432.6 557.8v-418.8c0-12-4.8-24-13.5-31.9l-217.1-217.4c-8.8-8.8-20-13.6-32-13.6h-418.2v-394.8c0-20.8 16.8-37.6 37.5-37.6h585.1c277.7-0.8 490.8 223 490.8 556.3z'
fill='#ff5252'
/>
<path
d='m1099.9 663.4c8.7 8.7 13.5 19.9 13.5 31.9v418.8h-643.3c-20.7 0-37.5-16.8-37.5-37.5v-643.4h418.2c12 0 24 4.8 32 13.6z'
fill='#000000'
/>
</svg>
)
}
export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg

View File

@@ -32,6 +32,7 @@ import {
DevinIcon,
DiscordIcon,
DocumentIcon,
DocuSignIcon,
DropboxIcon,
DsPyIcon,
DubIcon,
@@ -198,6 +199,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
datadog: DatadogIcon,
devin: DevinIcon,
discord: DiscordIcon,
docusign: DocuSignIcon,
dropbox: DropboxIcon,
dspy: DsPyIcon,
dub: DubIcon,

View File

@@ -0,0 +1,229 @@
---
title: DocuSign
description: Send documents for e-signature via DocuSign
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="docusign"
color="#4C00FF"
/>
{/* MANUAL-CONTENT-START:intro */}
[DocuSign](https://www.docusign.com) is the world's leading e-signature platform, enabling businesses to send, sign, and manage agreements digitally. With its powerful eSignature REST API, DocuSign supports the full document lifecycle from creation through completion.
With the DocuSign integration in Sim, you can:
- **Send envelopes**: Create and send documents for e-signature with custom recipients and signing tabs
- **Use templates**: Send envelopes from pre-configured DocuSign templates with role assignments
- **Track status**: Get envelope details including signing progress, timestamps, and recipient status
- **List envelopes**: Search and filter envelopes by date range, status, and text
- **Download documents**: Retrieve signed documents as base64-encoded files
- **Manage recipients**: View signer and CC recipient details and signing status
- **Void envelopes**: Cancel in-progress envelopes with a reason
In Sim, the DocuSign integration enables your agents to automate document workflows end-to-end. Agents can generate agreements, send them for signature, monitor completion, and retrieve signed copies—powering contract management, HR onboarding, sales closings, and compliance processes.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Create and send envelopes for e-signature, use templates, check signing status, download signed documents, and manage recipients with DocuSign.
## Tools
### `docusign_send_envelope`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `envelopeId` | string | Envelope ID |
| `status` | string | Envelope or operation status |
| `envelopes` | json | Array of envelopes |
| `templates` | json | Array of templates |
| `signers` | json | Array of signer recipients |
| `carbonCopies` | json | Array of CC recipients |
| `base64Content` | string | Base64-encoded document content |
| `mimeType` | string | Document MIME type |
| `fileName` | string | Document file name |
| `emailSubject` | string | Envelope email subject |
| `totalSetSize` | number | Total matching results |
| `resultSetSize` | number | Results returned |
### `docusign_create_from_template`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `envelopeId` | string | Envelope ID |
| `status` | string | Envelope or operation status |
| `envelopes` | json | Array of envelopes |
| `templates` | json | Array of templates |
| `signers` | json | Array of signer recipients |
| `carbonCopies` | json | Array of CC recipients |
| `base64Content` | string | Base64-encoded document content |
| `mimeType` | string | Document MIME type |
| `fileName` | string | Document file name |
| `emailSubject` | string | Envelope email subject |
| `totalSetSize` | number | Total matching results |
| `resultSetSize` | number | Results returned |
### `docusign_get_envelope`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `envelopeId` | string | Envelope ID |
| `status` | string | Envelope or operation status |
| `envelopes` | json | Array of envelopes |
| `templates` | json | Array of templates |
| `signers` | json | Array of signer recipients |
| `carbonCopies` | json | Array of CC recipients |
| `base64Content` | string | Base64-encoded document content |
| `mimeType` | string | Document MIME type |
| `fileName` | string | Document file name |
| `emailSubject` | string | Envelope email subject |
| `totalSetSize` | number | Total matching results |
| `resultSetSize` | number | Results returned |
### `docusign_list_envelopes`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `envelopeId` | string | Envelope ID |
| `status` | string | Envelope or operation status |
| `envelopes` | json | Array of envelopes |
| `templates` | json | Array of templates |
| `signers` | json | Array of signer recipients |
| `carbonCopies` | json | Array of CC recipients |
| `base64Content` | string | Base64-encoded document content |
| `mimeType` | string | Document MIME type |
| `fileName` | string | Document file name |
| `emailSubject` | string | Envelope email subject |
| `totalSetSize` | number | Total matching results |
| `resultSetSize` | number | Results returned |
### `docusign_void_envelope`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `envelopeId` | string | Envelope ID |
| `status` | string | Envelope or operation status |
| `envelopes` | json | Array of envelopes |
| `templates` | json | Array of templates |
| `signers` | json | Array of signer recipients |
| `carbonCopies` | json | Array of CC recipients |
| `base64Content` | string | Base64-encoded document content |
| `mimeType` | string | Document MIME type |
| `fileName` | string | Document file name |
| `emailSubject` | string | Envelope email subject |
| `totalSetSize` | number | Total matching results |
| `resultSetSize` | number | Results returned |
### `docusign_download_document`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `envelopeId` | string | Envelope ID |
| `status` | string | Envelope or operation status |
| `envelopes` | json | Array of envelopes |
| `templates` | json | Array of templates |
| `signers` | json | Array of signer recipients |
| `carbonCopies` | json | Array of CC recipients |
| `base64Content` | string | Base64-encoded document content |
| `mimeType` | string | Document MIME type |
| `fileName` | string | Document file name |
| `emailSubject` | string | Envelope email subject |
| `totalSetSize` | number | Total matching results |
| `resultSetSize` | number | Results returned |
### `docusign_list_templates`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `envelopeId` | string | Envelope ID |
| `status` | string | Envelope or operation status |
| `envelopes` | json | Array of envelopes |
| `templates` | json | Array of templates |
| `signers` | json | Array of signer recipients |
| `carbonCopies` | json | Array of CC recipients |
| `base64Content` | string | Base64-encoded document content |
| `mimeType` | string | Document MIME type |
| `fileName` | string | Document file name |
| `emailSubject` | string | Envelope email subject |
| `totalSetSize` | number | Total matching results |
| `resultSetSize` | number | Results returned |
### `docusign_list_recipients`
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `envelopeId` | string | Envelope ID |
| `status` | string | Envelope or operation status |
| `envelopes` | json | Array of envelopes |
| `templates` | json | Array of templates |
| `signers` | json | Array of signer recipients |
| `carbonCopies` | json | Array of CC recipients |
| `base64Content` | string | Base64-encoded document content |
| `mimeType` | string | Document MIME type |
| `fileName` | string | Document file name |
| `emailSubject` | string | Envelope email subject |
| `totalSetSize` | number | Total matching results |
| `resultSetSize` | number | Results returned |

View File

@@ -27,6 +27,7 @@
"datadog",
"devin",
"discord",
"docusign",
"dropbox",
"dspy",
"dub",

View File

@@ -0,0 +1,466 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { FileInputSchema } from '@/lib/uploads/utils/file-schemas'
import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
const logger = createLogger('DocuSignAPI')
interface DocuSignAccountInfo {
accountId: string
baseUri: string
}
/**
* Resolves the user's DocuSign account info from their access token
* by calling the DocuSign userinfo endpoint.
*/
async function resolveAccount(accessToken: string): Promise<DocuSignAccountInfo> {
const response = await fetch('https://account.docusign.com/oauth/userinfo', {
headers: { Authorization: `Bearer ${accessToken}` },
})
if (!response.ok) {
const errorText = await response.text()
logger.error('Failed to resolve DocuSign account', {
status: response.status,
error: errorText,
})
throw new Error(`Failed to resolve DocuSign account: ${response.status}`)
}
const data = await response.json()
const accounts = data.accounts ?? []
const defaultAccount = accounts.find((a: { is_default: boolean }) => a.is_default) ?? accounts[0]
if (!defaultAccount) {
throw new Error('No DocuSign accounts found for this user')
}
const baseUri = defaultAccount.base_uri
if (!baseUri) {
throw new Error('DocuSign account is missing base_uri')
}
return {
accountId: defaultAccount.account_id,
baseUri,
}
}
export async function POST(request: NextRequest) {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const { accessToken, operation, ...params } = body
if (!accessToken) {
return NextResponse.json({ success: false, error: 'Access token is required' }, { status: 400 })
}
if (!operation) {
return NextResponse.json({ success: false, error: 'Operation is required' }, { status: 400 })
}
try {
const account = await resolveAccount(accessToken)
const apiBase = `${account.baseUri}/restapi/v2.1/accounts/${account.accountId}`
const headers: Record<string, string> = {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
}
switch (operation) {
case 'send_envelope':
return await handleSendEnvelope(apiBase, headers, params)
case 'create_from_template':
return await handleCreateFromTemplate(apiBase, headers, params)
case 'get_envelope':
return await handleGetEnvelope(apiBase, headers, params)
case 'list_envelopes':
return await handleListEnvelopes(apiBase, headers, params)
case 'void_envelope':
return await handleVoidEnvelope(apiBase, headers, params)
case 'download_document':
return await handleDownloadDocument(apiBase, headers, params)
case 'list_templates':
return await handleListTemplates(apiBase, headers, params)
case 'list_recipients':
return await handleListRecipients(apiBase, headers, params)
default:
return NextResponse.json(
{ success: false, error: `Unknown operation: ${operation}` },
{ status: 400 }
)
}
} catch (error) {
logger.error('DocuSign API error', { operation, error })
const message = error instanceof Error ? error.message : 'Internal server error'
return NextResponse.json({ success: false, error: message }, { status: 500 })
}
}
async function handleSendEnvelope(
apiBase: string,
headers: Record<string, string>,
params: Record<string, unknown>
) {
const { signerEmail, signerName, emailSubject, emailBody, ccEmail, ccName, file, status } = params
if (!signerEmail || !signerName || !emailSubject) {
return NextResponse.json(
{ success: false, error: 'signerEmail, signerName, and emailSubject are required' },
{ status: 400 }
)
}
let documentBase64 = ''
let documentName = 'document.pdf'
if (file) {
try {
const parsed = FileInputSchema.parse(file)
const userFiles = processFilesToUserFiles([parsed as RawFileInput], 'docusign-send', logger)
if (userFiles.length > 0) {
const userFile = userFiles[0]
const buffer = await downloadFileFromStorage(userFile, 'docusign-send', logger)
documentBase64 = buffer.toString('base64')
documentName = userFile.name
}
} catch (fileError) {
logger.error('Failed to process file for DocuSign envelope', { fileError })
return NextResponse.json(
{ success: false, error: 'Failed to process uploaded file' },
{ status: 400 }
)
}
}
const envelopeBody: Record<string, unknown> = {
emailSubject,
status: (status as string) || 'sent',
recipients: {
signers: [
{
email: signerEmail,
name: signerName,
recipientId: '1',
routingOrder: '1',
tabs: {
signHereTabs: [
{
anchorString: '/sig1/',
anchorUnits: 'pixels',
anchorXOffset: '0',
anchorYOffset: '0',
},
],
dateSignedTabs: [
{
anchorString: '/date1/',
anchorUnits: 'pixels',
anchorXOffset: '0',
anchorYOffset: '0',
},
],
},
},
],
carbonCopies: ccEmail
? [
{
email: ccEmail,
name: ccName || (ccEmail as string),
recipientId: '2',
routingOrder: '2',
},
]
: [],
},
}
if (emailBody) {
envelopeBody.emailBlurb = emailBody
}
if (documentBase64) {
envelopeBody.documents = [
{
documentBase64,
name: documentName,
fileExtension: documentName.split('.').pop() || 'pdf',
documentId: '1',
},
]
} else if (((status as string) || 'sent') === 'sent') {
return NextResponse.json(
{ success: false, error: 'A document file is required to send an envelope' },
{ status: 400 }
)
}
const response = await fetch(`${apiBase}/envelopes`, {
method: 'POST',
headers,
body: JSON.stringify(envelopeBody),
})
const data = await response.json()
if (!response.ok) {
logger.error('DocuSign send envelope failed', { data, status: response.status })
return NextResponse.json(
{ success: false, error: data.message || data.errorCode || 'Failed to send envelope' },
{ status: response.status }
)
}
return NextResponse.json(data)
}
async function handleCreateFromTemplate(
apiBase: string,
headers: Record<string, string>,
params: Record<string, unknown>
) {
const { templateId, emailSubject, emailBody, templateRoles, status } = params
if (!templateId) {
return NextResponse.json({ success: false, error: 'templateId is required' }, { status: 400 })
}
let parsedRoles: unknown[] = []
if (templateRoles) {
if (typeof templateRoles === 'string') {
try {
parsedRoles = JSON.parse(templateRoles)
} catch {
return NextResponse.json(
{ success: false, error: 'Invalid JSON for templateRoles' },
{ status: 400 }
)
}
} else if (Array.isArray(templateRoles)) {
parsedRoles = templateRoles
}
}
const envelopeBody: Record<string, unknown> = {
templateId,
status: (status as string) || 'sent',
templateRoles: parsedRoles,
}
if (emailSubject) envelopeBody.emailSubject = emailSubject
if (emailBody) envelopeBody.emailBlurb = emailBody
const response = await fetch(`${apiBase}/envelopes`, {
method: 'POST',
headers,
body: JSON.stringify(envelopeBody),
})
const data = await response.json()
if (!response.ok) {
logger.error('DocuSign create from template failed', { data, status: response.status })
return NextResponse.json(
{
success: false,
error: data.message || data.errorCode || 'Failed to create envelope from template',
},
{ status: response.status }
)
}
return NextResponse.json(data)
}
async function handleGetEnvelope(
apiBase: string,
headers: Record<string, string>,
params: Record<string, unknown>
) {
const { envelopeId } = params
if (!envelopeId) {
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
}
const response = await fetch(
`${apiBase}/envelopes/${(envelopeId as string).trim()}?include=recipients,documents`,
{ headers }
)
const data = await response.json()
if (!response.ok) {
return NextResponse.json(
{ success: false, error: data.message || data.errorCode || 'Failed to get envelope' },
{ status: response.status }
)
}
return NextResponse.json(data)
}
async function handleListEnvelopes(
apiBase: string,
headers: Record<string, string>,
params: Record<string, unknown>
) {
const queryParams = new URLSearchParams()
const fromDate = params.fromDate as string | undefined
if (fromDate) {
queryParams.append('from_date', fromDate)
} else {
const thirtyDaysAgo = new Date()
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
queryParams.append('from_date', thirtyDaysAgo.toISOString())
}
if (params.toDate) queryParams.append('to_date', params.toDate as string)
if (params.envelopeStatus) queryParams.append('status', params.envelopeStatus as string)
if (params.searchText) queryParams.append('search_text', params.searchText as string)
if (params.count) queryParams.append('count', params.count as string)
const response = await fetch(`${apiBase}/envelopes?${queryParams}`, { headers })
const data = await response.json()
if (!response.ok) {
return NextResponse.json(
{ success: false, error: data.message || data.errorCode || 'Failed to list envelopes' },
{ status: response.status }
)
}
return NextResponse.json(data)
}
async function handleVoidEnvelope(
apiBase: string,
headers: Record<string, string>,
params: Record<string, unknown>
) {
const { envelopeId, voidedReason } = params
if (!envelopeId) {
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
}
if (!voidedReason) {
return NextResponse.json({ success: false, error: 'voidedReason is required' }, { status: 400 })
}
const response = await fetch(`${apiBase}/envelopes/${(envelopeId as string).trim()}`, {
method: 'PUT',
headers,
body: JSON.stringify({ status: 'voided', voidedReason }),
})
const data = await response.json()
if (!response.ok) {
return NextResponse.json(
{ success: false, error: data.message || data.errorCode || 'Failed to void envelope' },
{ status: response.status }
)
}
return NextResponse.json({ envelopeId, status: 'voided' })
}
async function handleDownloadDocument(
apiBase: string,
headers: Record<string, string>,
params: Record<string, unknown>
) {
const { envelopeId, documentId } = params
if (!envelopeId) {
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
}
const docId = (documentId as string) || 'combined'
const response = await fetch(
`${apiBase}/envelopes/${(envelopeId as string).trim()}/documents/${docId}`,
{
headers: { Authorization: headers.Authorization },
}
)
if (!response.ok) {
let errorText = ''
try {
errorText = await response.text()
} catch {
// ignore
}
return NextResponse.json(
{ success: false, error: `Failed to download document: ${response.status} ${errorText}` },
{ status: response.status }
)
}
const contentType = response.headers.get('content-type') || 'application/pdf'
const contentDisposition = response.headers.get('content-disposition') || ''
let fileName = `document-${docId}.pdf`
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
if (filenameMatch) {
fileName = filenameMatch[1].replace(/['"]/g, '')
}
const buffer = Buffer.from(await response.arrayBuffer())
const base64Content = buffer.toString('base64')
return NextResponse.json({ base64Content, mimeType: contentType, fileName })
}
async function handleListTemplates(
apiBase: string,
headers: Record<string, string>,
params: Record<string, unknown>
) {
const queryParams = new URLSearchParams()
if (params.searchText) queryParams.append('search_text', params.searchText as string)
if (params.count) queryParams.append('count', params.count as string)
const queryString = queryParams.toString()
const url = queryString ? `${apiBase}/templates?${queryString}` : `${apiBase}/templates`
const response = await fetch(url, { headers })
const data = await response.json()
if (!response.ok) {
return NextResponse.json(
{ success: false, error: data.message || data.errorCode || 'Failed to list templates' },
{ status: response.status }
)
}
return NextResponse.json(data)
}
async function handleListRecipients(
apiBase: string,
headers: Record<string, string>,
params: Record<string, unknown>
) {
const { envelopeId } = params
if (!envelopeId) {
return NextResponse.json({ success: false, error: 'envelopeId is required' }, { status: 400 })
}
const response = await fetch(`${apiBase}/envelopes/${(envelopeId as string).trim()}/recipients`, {
headers,
})
const data = await response.json()
if (!response.ok) {
return NextResponse.json(
{ success: false, error: data.message || data.errorCode || 'Failed to list recipients' },
{ status: response.status }
)
}
return NextResponse.json(data)
}

View File

@@ -0,0 +1,372 @@
import { DocuSignIcon } from '@/components/icons'
import { getScopesForService } from '@/lib/oauth/utils'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import { normalizeFileInput } from '@/blocks/utils'
import type { DocuSignResponse } from '@/tools/docusign/types'
export const DocuSignBlock: BlockConfig<DocuSignResponse> = {
type: 'docusign',
name: 'DocuSign',
description: 'Send documents for e-signature via DocuSign',
longDescription:
'Create and send envelopes for e-signature, use templates, check signing status, download signed documents, and manage recipients with DocuSign.',
docsLink: 'https://docs.sim.ai/tools/docusign',
category: 'tools',
bgColor: '#4C00FF',
icon: DocuSignIcon,
authMode: AuthMode.OAuth,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Send Envelope', id: 'send_envelope' },
{ label: 'Send from Template', id: 'create_from_template' },
{ label: 'Get Envelope', id: 'get_envelope' },
{ label: 'List Envelopes', id: 'list_envelopes' },
{ label: 'Void Envelope', id: 'void_envelope' },
{ label: 'Download Document', id: 'download_document' },
{ label: 'List Templates', id: 'list_templates' },
{ label: 'List Recipients', id: 'list_recipients' },
],
value: () => 'send_envelope',
},
{
id: 'credential',
title: 'DocuSign Account',
type: 'oauth-input',
canonicalParamId: 'oauthCredential',
mode: 'basic',
serviceId: 'docusign',
requiredScopes: getScopesForService('docusign'),
placeholder: 'Select DocuSign account',
required: true,
},
{
id: 'manualCredential',
title: 'DocuSign Account',
type: 'short-input',
canonicalParamId: 'oauthCredential',
mode: 'advanced',
placeholder: 'Enter credential ID',
required: true,
},
// Send Envelope fields
{
id: 'emailSubject',
title: 'Email Subject',
type: 'short-input',
placeholder: 'Please sign this document',
condition: { field: 'operation', value: ['send_envelope', 'create_from_template'] },
required: { field: 'operation', value: 'send_envelope' },
},
{
id: 'emailBody',
title: 'Email Body',
type: 'long-input',
placeholder: 'Optional message to include in the email',
condition: { field: 'operation', value: ['send_envelope', 'create_from_template'] },
mode: 'advanced',
},
{
id: 'signerEmail',
title: 'Signer Email',
type: 'short-input',
placeholder: 'signer@example.com',
condition: { field: 'operation', value: 'send_envelope' },
required: { field: 'operation', value: 'send_envelope' },
},
{
id: 'signerName',
title: 'Signer Name',
type: 'short-input',
placeholder: 'John Doe',
condition: { field: 'operation', value: 'send_envelope' },
required: { field: 'operation', value: 'send_envelope' },
},
{
id: 'uploadDocument',
title: 'Document',
type: 'file-upload',
canonicalParamId: 'documentFile',
placeholder: 'Upload document for signature',
mode: 'basic',
multiple: false,
condition: { field: 'operation', value: 'send_envelope' },
},
{
id: 'documentRef',
title: 'Document',
type: 'short-input',
canonicalParamId: 'documentFile',
placeholder: 'Reference file from another block',
mode: 'advanced',
condition: { field: 'operation', value: 'send_envelope' },
},
{
id: 'ccEmail',
title: 'CC Email',
type: 'short-input',
placeholder: 'cc@example.com',
condition: { field: 'operation', value: 'send_envelope' },
mode: 'advanced',
},
{
id: 'ccName',
title: 'CC Name',
type: 'short-input',
placeholder: 'CC recipient name',
condition: { field: 'operation', value: 'send_envelope' },
mode: 'advanced',
},
{
id: 'envelopeStatus',
title: 'Status',
type: 'dropdown',
options: [
{ label: 'Send Immediately', id: 'sent' },
{ label: 'Save as Draft', id: 'created' },
],
value: () => 'sent',
condition: { field: 'operation', value: ['send_envelope', 'create_from_template'] },
mode: 'advanced',
},
// Send from Template fields
{
id: 'templateId',
title: 'Template ID',
type: 'short-input',
placeholder: 'DocuSign template ID',
condition: { field: 'operation', value: 'create_from_template' },
required: { field: 'operation', value: 'create_from_template' },
},
{
id: 'templateRoles',
title: 'Template Roles',
type: 'long-input',
placeholder: '[{"roleName":"Signer","name":"John Doe","email":"john@example.com"}]',
condition: { field: 'operation', value: 'create_from_template' },
required: { field: 'operation', value: 'create_from_template' },
wandConfig: {
enabled: true,
prompt:
'Generate a JSON array of DocuSign template role objects. Each role needs: roleName (must match the template role name), name (full name), email (email address). Return ONLY the JSON array.',
generationType: 'json-object',
},
},
// Envelope ID field (shared across multiple operations)
{
id: 'envelopeId',
title: 'Envelope ID',
type: 'short-input',
placeholder: 'DocuSign envelope ID',
condition: {
field: 'operation',
value: ['get_envelope', 'void_envelope', 'download_document', 'list_recipients'],
},
required: {
field: 'operation',
value: ['get_envelope', 'void_envelope', 'download_document', 'list_recipients'],
},
},
// Void Envelope fields
{
id: 'voidedReason',
title: 'Void Reason',
type: 'short-input',
placeholder: 'Reason for voiding this envelope',
condition: { field: 'operation', value: 'void_envelope' },
required: { field: 'operation', value: 'void_envelope' },
},
// Download Document fields
{
id: 'documentId',
title: 'Document ID',
type: 'short-input',
placeholder: '"combined" for all docs, or specific document ID',
condition: { field: 'operation', value: 'download_document' },
mode: 'advanced',
},
// List Envelopes filters
{
id: 'fromDate',
title: 'From Date',
type: 'short-input',
placeholder: 'ISO 8601 date (defaults to 30 days ago)',
condition: { field: 'operation', value: 'list_envelopes' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
generationType: 'timestamp',
},
},
{
id: 'toDate',
title: 'To Date',
type: 'short-input',
placeholder: 'ISO 8601 date',
condition: { field: 'operation', value: 'list_envelopes' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: 'Generate an ISO 8601 timestamp. Return ONLY the timestamp string.',
generationType: 'timestamp',
},
},
{
id: 'listEnvelopeStatus',
title: 'Status Filter',
type: 'dropdown',
options: [
{ label: 'All', id: '' },
{ label: 'Created', id: 'created' },
{ label: 'Sent', id: 'sent' },
{ label: 'Delivered', id: 'delivered' },
{ label: 'Completed', id: 'completed' },
{ label: 'Declined', id: 'declined' },
{ label: 'Voided', id: 'voided' },
],
value: () => '',
condition: { field: 'operation', value: 'list_envelopes' },
mode: 'advanced',
},
{
id: 'searchText',
title: 'Search',
type: 'short-input',
placeholder: 'Search envelopes or templates',
condition: { field: 'operation', value: ['list_envelopes', 'list_templates'] },
mode: 'advanced',
},
{
id: 'count',
title: 'Max Results',
type: 'short-input',
placeholder: '25',
condition: { field: 'operation', value: ['list_envelopes', 'list_templates'] },
mode: 'advanced',
},
],
tools: {
access: [
'docusign_send_envelope',
'docusign_create_from_template',
'docusign_get_envelope',
'docusign_list_envelopes',
'docusign_void_envelope',
'docusign_download_document',
'docusign_list_templates',
'docusign_list_recipients',
],
config: {
tool: (params) => `docusign_${params.operation}`,
params: (params) => {
const { oauthCredential, operation, documentFile, listEnvelopeStatus, ...rest } = params
const cleanParams: Record<string, unknown> = {
oauthCredential,
}
const file = normalizeFileInput(documentFile, { single: true })
if (file) {
cleanParams.file = file
}
if (listEnvelopeStatus && operation === 'list_envelopes') {
cleanParams.envelopeStatus = listEnvelopeStatus
}
if (operation === 'create_from_template') {
cleanParams.status = rest.envelopeStatus
} else if (operation === 'send_envelope') {
cleanParams.status = rest.envelopeStatus
}
const excludeKeys = ['envelopeStatus']
for (const [key, value] of Object.entries(rest)) {
if (value !== undefined && value !== null && value !== '' && !excludeKeys.includes(key)) {
cleanParams[key] = value
}
}
return cleanParams
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
oauthCredential: { type: 'string', description: 'DocuSign access token' },
emailSubject: { type: 'string', description: 'Email subject for the envelope' },
emailBody: { type: 'string', description: 'Email body message' },
signerEmail: { type: 'string', description: 'Signer email address' },
signerName: { type: 'string', description: 'Signer full name' },
documentFile: { type: 'string', description: 'Document file for signature' },
ccEmail: { type: 'string', description: 'CC recipient email' },
ccName: { type: 'string', description: 'CC recipient name' },
templateId: { type: 'string', description: 'DocuSign template ID' },
templateRoles: { type: 'string', description: 'JSON array of template roles' },
envelopeId: { type: 'string', description: 'Envelope ID' },
voidedReason: { type: 'string', description: 'Reason for voiding' },
documentId: { type: 'string', description: 'Document ID to download' },
fromDate: { type: 'string', description: 'Start date filter' },
toDate: { type: 'string', description: 'End date filter' },
searchText: { type: 'string', description: 'Search text filter' },
count: { type: 'string', description: 'Max results to return' },
},
outputs: {
envelopeId: { type: 'string', description: 'Envelope ID' },
status: {
type: 'string',
description: 'Envelope status (created, sent, delivered, completed, declined, voided)',
},
statusDateTime: { type: 'string', description: 'ISO 8601 datetime of status change' },
uri: { type: 'string', description: 'Envelope URI path' },
emailSubject: { type: 'string', description: 'Envelope email subject' },
sentDateTime: { type: 'string', description: 'ISO 8601 datetime when envelope was sent' },
completedDateTime: { type: 'string', description: 'ISO 8601 datetime when signing completed' },
createdDateTime: { type: 'string', description: 'ISO 8601 datetime when envelope was created' },
statusChangedDateTime: {
type: 'string',
description: 'ISO 8601 datetime of last status change',
},
voidedReason: { type: 'string', description: 'Reason the envelope was voided' },
signerCount: { type: 'number', description: 'Number of signers on the envelope' },
documentCount: { type: 'number', description: 'Number of documents in the envelope' },
envelopes: {
type: 'json',
description:
'Array of envelopes (envelopeId, status, emailSubject, sentDateTime, completedDateTime, createdDateTime, statusChangedDateTime)',
},
templates: {
type: 'json',
description:
'Array of templates (templateId, name, description, shared, created, lastModified)',
},
signers: {
type: 'json',
description:
'Array of signer recipients (recipientId, name, email, status, signedDateTime, deliveredDateTime)',
},
carbonCopies: {
type: 'json',
description: 'Array of CC recipients (recipientId, name, email, status)',
},
base64Content: { type: 'string', description: 'Base64-encoded document content' },
mimeType: { type: 'string', description: 'Document MIME type' },
fileName: { type: 'string', description: 'Document file name' },
totalSetSize: { type: 'number', description: 'Total matching results' },
resultSetSize: { type: 'number', description: 'Results returned in this response' },
},
}

View File

@@ -29,6 +29,7 @@ import { DatabricksBlock } from '@/blocks/blocks/databricks'
import { DatadogBlock } from '@/blocks/blocks/datadog'
import { DevinBlock } from '@/blocks/blocks/devin'
import { DiscordBlock } from '@/blocks/blocks/discord'
import { DocuSignBlock } from '@/blocks/blocks/docusign'
import { DropboxBlock } from '@/blocks/blocks/dropbox'
import { DSPyBlock } from '@/blocks/blocks/dspy'
import { DubBlock } from '@/blocks/blocks/dub'
@@ -232,6 +233,7 @@ export const registry: Record<string, BlockConfig> = {
datadog: DatadogBlock,
devin: DevinBlock,
discord: DiscordBlock,
docusign: DocuSignBlock,
dropbox: DropboxBlock,
dspy: DSPyBlock,
dub: DubBlock,

View File

@@ -1146,6 +1146,25 @@ export function DevinIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function DocuSignIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 1547 1549' xmlns='http://www.w3.org/2000/svg'>
<path
d='m1113.4 1114.9v395.6c0 20.8-16.7 37.6-37.5 37.6h-1038.4c-20.7 0-37.5-16.8-37.5-37.6v-1039c0-20.7 16.8-37.5 37.5-37.5h394.3v643.4c0 20.7 16.8 37.5 37.5 37.5z'
fill='#4c00ff'
/>
<path
d='m1546 557.1c0 332.4-193.9 557-432.6 557.8v-418.8c0-12-4.8-24-13.5-31.9l-217.1-217.4c-8.8-8.8-20-13.6-32-13.6h-418.2v-394.8c0-20.8 16.8-37.6 37.5-37.6h585.1c277.7-0.8 490.8 223 490.8 556.3z'
fill='#ff5252'
/>
<path
d='m1099.9 663.4c8.7 8.7 13.5 19.9 13.5 31.9v418.8h-643.3c-20.7 0-37.5-16.8-37.5-37.5v-643.4h418.2c12 0 24 4.8 32 13.6z'
fill='#000000'
/>
</svg>
)
}
export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg

View File

@@ -488,6 +488,7 @@ export const auth = betterAuth({
'shopify',
'trello',
'calcom',
'docusign',
...SSO_TRUSTED_PROVIDERS,
],
},
@@ -2593,6 +2594,64 @@ export const auth = betterAuth({
},
},
// DocuSign provider
{
providerId: 'docusign',
clientId: env.DOCUSIGN_CLIENT_ID as string,
clientSecret: env.DOCUSIGN_CLIENT_SECRET as string,
authorizationUrl: 'https://account.docusign.com/oauth/auth',
tokenUrl: 'https://account.docusign.com/oauth/token',
userInfoUrl: 'https://account.docusign.com/oauth/userinfo',
scopes: getCanonicalScopesForProvider('docusign'),
responseType: 'code',
accessType: 'offline',
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/docusign`,
getUserInfo: async (tokens) => {
try {
logger.info('Fetching DocuSign user profile')
const response = await fetch('https://account.docusign.com/oauth/userinfo', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
})
if (!response.ok) {
await response.text().catch(() => {})
logger.error('Failed to fetch DocuSign user info', {
status: response.status,
statusText: response.statusText,
})
throw new Error('Failed to fetch user info')
}
const data = await response.json()
const accounts = data.accounts ?? []
const defaultAccount =
accounts.find((a: { is_default: boolean }) => a.is_default) ?? accounts[0]
const accountName = defaultAccount?.account_name || 'DocuSign Account'
if (data.scope) {
tokens.scopes = data.scope.split(/\s+/).filter(Boolean)
}
return {
id: `${data.sub}-${crypto.randomUUID()}`,
name: data.name || accountName,
email: data.email || `${data.sub}@docusign.com`,
emailVerified: true,
image: undefined,
createdAt: new Date(),
updatedAt: new Date(),
}
} catch (error) {
logger.error('Error in DocuSign getUserInfo:', { error })
return null
}
},
},
// Cal.com provider
{
providerId: 'calcom',

View File

@@ -263,6 +263,8 @@ export const env = createEnv({
NOTION_CLIENT_SECRET: z.string().optional(), // Notion OAuth client secret
DISCORD_CLIENT_ID: z.string().optional(), // Discord OAuth client ID
DISCORD_CLIENT_SECRET: z.string().optional(), // Discord OAuth client secret
DOCUSIGN_CLIENT_ID: z.string().optional(), // DocuSign OAuth client ID
DOCUSIGN_CLIENT_SECRET: z.string().optional(), // DocuSign OAuth client secret
MICROSOFT_CLIENT_ID: z.string().optional(), // Microsoft OAuth client ID for Office 365/Teams
MICROSOFT_CLIENT_SECRET: z.string().optional(), // Microsoft OAuth client secret
HUBSPOT_CLIENT_ID: z.string().optional(), // HubSpot OAuth client ID

View File

@@ -5,6 +5,7 @@ import {
AttioIcon,
CalComIcon,
ConfluenceIcon,
DocuSignIcon,
DropboxIcon,
GmailIcon,
GoogleAdsIcon,
@@ -779,6 +780,21 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
},
defaultService: 'calcom',
},
docusign: {
name: 'DocuSign',
icon: DocuSignIcon,
services: {
docusign: {
name: 'DocuSign',
description: 'Send documents for e-signature with DocuSign.',
providerId: 'docusign',
icon: DocuSignIcon,
baseProviderIcon: DocuSignIcon,
scopes: ['signature', 'extended'],
},
},
defaultService: 'docusign',
},
pipedrive: {
name: 'Pipedrive',
icon: PipedriveIcon,
@@ -1109,6 +1125,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
useBasicAuth: false,
}
}
case 'docusign': {
const { clientId, clientSecret } = getCredentials(
env.DOCUSIGN_CLIENT_ID,
env.DOCUSIGN_CLIENT_SECRET
)
return {
tokenEndpoint: 'https://account.docusign.com/oauth/token',
clientId,
clientSecret,
useBasicAuth: true,
supportsRefreshTokenRotation: false,
}
}
case 'dropbox': {
const { clientId, clientSecret } = getCredentials(
env.DROPBOX_CLIENT_ID,

View File

@@ -47,6 +47,7 @@ export type OAuthProvider =
| 'wordpress'
| 'spotify'
| 'calcom'
| 'docusign'
export type OAuthService =
| 'google'
@@ -94,6 +95,7 @@ export type OAuthService =
| 'wordpress'
| 'spotify'
| 'calcom'
| 'docusign'
| 'github'
export interface OAuthProviderConfig {

View File

@@ -395,6 +395,10 @@ export const SCOPE_DESCRIPTIONS: Record<string, string> = {
'user-read-playback-position': 'View playback position in podcasts',
'ugc-image-upload': 'Upload images to Spotify playlists',
// DocuSign scopes
signature: 'Create and send envelopes for e-signature',
extended: 'Extended access to DocuSign account features',
// Attio scopes
'record_permission:read-write': 'Read and write CRM records',
'object_configuration:read-write': 'Read and manage object schemas',

View File

@@ -0,0 +1,99 @@
import type {
DocuSignCreateFromTemplateParams,
DocuSignCreateFromTemplateResponse,
} from '@/tools/docusign/types'
import type { ToolConfig } from '@/tools/types'
export const docusignCreateFromTemplateTool: ToolConfig<
DocuSignCreateFromTemplateParams,
DocuSignCreateFromTemplateResponse
> = {
id: 'docusign_create_from_template',
name: 'Send from DocuSign Template',
description: 'Create and send a DocuSign envelope using a pre-built template',
version: '1.0.0',
oauth: {
required: true,
provider: 'docusign',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'DocuSign OAuth access token',
},
templateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'DocuSign template ID to use',
},
emailSubject: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Override email subject (uses template default if not set)',
},
emailBody: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Override email body message',
},
templateRoles: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON array of template roles, e.g. [{"roleName":"Signer","name":"John","email":"john@example.com"}]',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Envelope status: "sent" to send immediately, "created" for draft (default: "sent")',
},
},
request: {
url: '/api/tools/docusign',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
accessToken: params.accessToken,
operation: 'create_from_template',
templateId: params.templateId,
emailSubject: params.emailSubject,
emailBody: params.emailBody,
templateRoles: params.templateRoles,
status: params.status,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.success === false) {
throw new Error(data.error || 'Failed to create envelope from template')
}
return {
success: true,
output: {
envelopeId: data.envelopeId ?? null,
status: data.status ?? null,
statusDateTime: data.statusDateTime ?? null,
uri: data.uri ?? null,
},
}
},
outputs: {
envelopeId: { type: 'string', description: 'Created envelope ID' },
status: { type: 'string', description: 'Envelope status' },
statusDateTime: { type: 'string', description: 'Status change datetime', optional: true },
uri: { type: 'string', description: 'Envelope URI', optional: true },
},
}

View File

@@ -0,0 +1,75 @@
import type {
DocuSignDownloadDocumentParams,
DocuSignDownloadDocumentResponse,
} from '@/tools/docusign/types'
import type { ToolConfig } from '@/tools/types'
export const docusignDownloadDocumentTool: ToolConfig<
DocuSignDownloadDocumentParams,
DocuSignDownloadDocumentResponse
> = {
id: 'docusign_download_document',
name: 'Download DocuSign Document',
description: 'Download a signed document from a completed DocuSign envelope',
version: '1.0.0',
oauth: {
required: true,
provider: 'docusign',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'DocuSign OAuth access token',
},
envelopeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The envelope ID containing the document',
},
documentId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Specific document ID to download, or "combined" for all documents merged (default: "combined")',
},
},
request: {
url: '/api/tools/docusign',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
accessToken: params.accessToken,
operation: 'download_document',
envelopeId: params.envelopeId,
documentId: params.documentId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.success === false) {
throw new Error(data.error || 'Failed to download document')
}
return {
success: true,
output: {
base64Content: data.base64Content ?? '',
mimeType: data.mimeType ?? 'application/pdf',
fileName: data.fileName ?? 'document.pdf',
},
}
},
outputs: {
base64Content: { type: 'string', description: 'Base64-encoded document content' },
mimeType: { type: 'string', description: 'MIME type of the document' },
fileName: { type: 'string', description: 'Original file name' },
},
}

View File

@@ -0,0 +1,85 @@
import type { DocuSignGetEnvelopeParams, DocuSignGetEnvelopeResponse } from '@/tools/docusign/types'
import type { ToolConfig } from '@/tools/types'
export const docusignGetEnvelopeTool: ToolConfig<
DocuSignGetEnvelopeParams,
DocuSignGetEnvelopeResponse
> = {
id: 'docusign_get_envelope',
name: 'Get DocuSign Envelope',
description: 'Get the details and status of a DocuSign envelope',
version: '1.0.0',
oauth: {
required: true,
provider: 'docusign',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'DocuSign OAuth access token',
},
envelopeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The envelope ID to retrieve',
},
},
request: {
url: '/api/tools/docusign',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
accessToken: params.accessToken,
operation: 'get_envelope',
envelopeId: params.envelopeId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.success === false) {
throw new Error(data.error || 'Failed to get envelope')
}
return {
success: true,
output: {
envelopeId: data.envelopeId ?? null,
status: data.status ?? null,
emailSubject: data.emailSubject ?? null,
sentDateTime: data.sentDateTime ?? null,
completedDateTime: data.completedDateTime ?? null,
createdDateTime: data.createdDateTime ?? null,
statusChangedDateTime: data.statusChangedDateTime ?? null,
voidedReason: data.voidedReason ?? null,
signerCount: data.recipients?.signers?.length ?? 0,
documentCount: data.envelopeDocuments?.length ?? 0,
},
}
},
outputs: {
envelopeId: { type: 'string', description: 'Envelope ID' },
status: {
type: 'string',
description: 'Envelope status (created, sent, delivered, completed, declined, voided)',
},
emailSubject: { type: 'string', description: 'Email subject line' },
sentDateTime: { type: 'string', description: 'When the envelope was sent', optional: true },
completedDateTime: {
type: 'string',
description: 'When all recipients completed signing',
optional: true,
},
createdDateTime: { type: 'string', description: 'When the envelope was created' },
statusChangedDateTime: { type: 'string', description: 'When the status last changed' },
voidedReason: { type: 'string', description: 'Reason the envelope was voided', optional: true },
signerCount: { type: 'number', description: 'Number of signers' },
documentCount: { type: 'number', description: 'Number of documents' },
},
}

View File

@@ -0,0 +1,9 @@
export { docusignCreateFromTemplateTool } from './create_from_template'
export { docusignDownloadDocumentTool } from './download_document'
export { docusignGetEnvelopeTool } from './get_envelope'
export { docusignListEnvelopesTool } from './list_envelopes'
export { docusignListRecipientsTool } from './list_recipients'
export { docusignListTemplatesTool } from './list_templates'
export { docusignSendEnvelopeTool } from './send_envelope'
export * from './types'
export { docusignVoidEnvelopeTool } from './void_envelope'

View File

@@ -0,0 +1,105 @@
import type {
DocuSignListEnvelopesParams,
DocuSignListEnvelopesResponse,
} from '@/tools/docusign/types'
import { ENVELOPES_ARRAY_OUTPUT } from '@/tools/docusign/types'
import type { ToolConfig } from '@/tools/types'
export const docusignListEnvelopesTool: ToolConfig<
DocuSignListEnvelopesParams,
DocuSignListEnvelopesResponse
> = {
id: 'docusign_list_envelopes',
name: 'List DocuSign Envelopes',
description: 'List envelopes from your DocuSign account with optional filters',
version: '1.0.0',
oauth: {
required: true,
provider: 'docusign',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'DocuSign OAuth access token',
},
fromDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Start date filter (ISO 8601). Defaults to 30 days ago',
},
toDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'End date filter (ISO 8601)',
},
envelopeStatus: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by status: created, sent, delivered, completed, declined, voided',
},
searchText: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search text to filter envelopes',
},
count: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of envelopes to return (default: 25)',
},
},
request: {
url: '/api/tools/docusign',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
accessToken: params.accessToken,
operation: 'list_envelopes',
fromDate: params.fromDate,
toDate: params.toDate,
envelopeStatus: params.envelopeStatus,
searchText: params.searchText,
count: params.count,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.success === false) {
throw new Error(data.error || 'Failed to list envelopes')
}
const envelopes = (data.envelopes ?? []).map((env: Record<string, unknown>) => ({
envelopeId: env.envelopeId ?? null,
status: env.status ?? null,
emailSubject: env.emailSubject ?? null,
sentDateTime: env.sentDateTime ?? null,
completedDateTime: env.completedDateTime ?? null,
createdDateTime: env.createdDateTime ?? null,
statusChangedDateTime: env.statusChangedDateTime ?? null,
}))
return {
success: true,
output: {
envelopes,
totalSetSize: Number(data.totalSetSize) || 0,
resultSetSize: Number(data.resultSetSize) || envelopes.length,
},
}
},
outputs: {
envelopes: ENVELOPES_ARRAY_OUTPUT,
totalSetSize: { type: 'number', description: 'Total number of matching envelopes' },
resultSetSize: { type: 'number', description: 'Number of envelopes returned in this response' },
},
}

View File

@@ -0,0 +1,92 @@
import type {
DocuSignListRecipientsParams,
DocuSignListRecipientsResponse,
} from '@/tools/docusign/types'
import { RECIPIENTS_ARRAY_OUTPUT } from '@/tools/docusign/types'
import type { ToolConfig } from '@/tools/types'
export const docusignListRecipientsTool: ToolConfig<
DocuSignListRecipientsParams,
DocuSignListRecipientsResponse
> = {
id: 'docusign_list_recipients',
name: 'List DocuSign Recipients',
description: 'Get the recipient status details for a DocuSign envelope',
version: '1.0.0',
oauth: {
required: true,
provider: 'docusign',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'DocuSign OAuth access token',
},
envelopeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The envelope ID to get recipients for',
},
},
request: {
url: '/api/tools/docusign',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
accessToken: params.accessToken,
operation: 'list_recipients',
envelopeId: params.envelopeId,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.success === false) {
throw new Error(data.error || 'Failed to list recipients')
}
const signers = (data.signers ?? []).map((s: Record<string, unknown>) => ({
recipientId: s.recipientId ?? null,
name: s.name ?? null,
email: s.email ?? null,
status: s.status ?? null,
signedDateTime: s.signedDateTime ?? null,
deliveredDateTime: s.deliveredDateTime ?? null,
}))
const carbonCopies = (data.carbonCopies ?? []).map((cc: Record<string, unknown>) => ({
recipientId: cc.recipientId ?? null,
name: cc.name ?? null,
email: cc.email ?? null,
status: cc.status ?? null,
}))
return {
success: true,
output: {
signers,
carbonCopies,
},
}
},
outputs: {
signers: RECIPIENTS_ARRAY_OUTPUT,
carbonCopies: {
type: 'array',
description: 'Array of carbon copy recipients',
items: {
type: 'object',
properties: {
recipientId: { type: 'string', description: 'Recipient ID' },
name: { type: 'string', description: 'Recipient name' },
email: { type: 'string', description: 'Recipient email' },
status: { type: 'string', description: 'Recipient status' },
},
},
},
},
}

View File

@@ -0,0 +1,83 @@
import type {
DocuSignListTemplatesParams,
DocuSignListTemplatesResponse,
} from '@/tools/docusign/types'
import { TEMPLATES_ARRAY_OUTPUT } from '@/tools/docusign/types'
import type { ToolConfig } from '@/tools/types'
export const docusignListTemplatesTool: ToolConfig<
DocuSignListTemplatesParams,
DocuSignListTemplatesResponse
> = {
id: 'docusign_list_templates',
name: 'List DocuSign Templates',
description: 'List available templates in your DocuSign account',
version: '1.0.0',
oauth: {
required: true,
provider: 'docusign',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'DocuSign OAuth access token',
},
searchText: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search text to filter templates by name',
},
count: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of templates to return',
},
},
request: {
url: '/api/tools/docusign',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
accessToken: params.accessToken,
operation: 'list_templates',
searchText: params.searchText,
count: params.count,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.success === false) {
throw new Error(data.error || 'Failed to list templates')
}
const templates = (data.envelopeTemplates ?? []).map((t: Record<string, unknown>) => ({
templateId: t.templateId ?? null,
name: t.name ?? null,
description: t.description ?? null,
shared: t.shared === 'true' || t.shared === true,
created: t.created ?? null,
lastModified: t.lastModified ?? null,
}))
return {
success: true,
output: {
templates,
totalSetSize: Number(data.totalSetSize) || 0,
resultSetSize: Number(data.resultSetSize) || templates.length,
},
}
},
outputs: {
templates: TEMPLATES_ARRAY_OUTPUT,
totalSetSize: { type: 'number', description: 'Total number of matching templates' },
resultSetSize: { type: 'number', description: 'Number of templates returned in this response' },
},
}

View File

@@ -0,0 +1,119 @@
import type {
DocuSignSendEnvelopeParams,
DocuSignSendEnvelopeResponse,
} from '@/tools/docusign/types'
import type { ToolConfig } from '@/tools/types'
export const docusignSendEnvelopeTool: ToolConfig<
DocuSignSendEnvelopeParams,
DocuSignSendEnvelopeResponse
> = {
id: 'docusign_send_envelope',
name: 'Send DocuSign Envelope',
description: 'Create and send a DocuSign envelope with a document for e-signature',
version: '1.0.0',
oauth: {
required: true,
provider: 'docusign',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'DocuSign OAuth access token',
},
emailSubject: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email subject for the envelope',
},
emailBody: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email body message',
},
signerEmail: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Email address of the signer',
},
signerName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Full name of the signer',
},
ccEmail: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address of carbon copy recipient',
},
ccName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Full name of carbon copy recipient',
},
file: {
type: 'file',
required: false,
visibility: 'user-or-llm',
description: 'Document file to send for signature',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Envelope status: "sent" to send immediately, "created" for draft (default: "sent")',
},
},
request: {
url: '/api/tools/docusign',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
accessToken: params.accessToken,
operation: 'send_envelope',
emailSubject: params.emailSubject,
emailBody: params.emailBody,
signerEmail: params.signerEmail,
signerName: params.signerName,
ccEmail: params.ccEmail,
ccName: params.ccName,
file: params.file,
status: params.status,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.success === false) {
throw new Error(data.error || 'Failed to send envelope')
}
return {
success: true,
output: {
envelopeId: data.envelopeId ?? null,
status: data.status ?? null,
statusDateTime: data.statusDateTime ?? null,
uri: data.uri ?? null,
},
}
},
outputs: {
envelopeId: { type: 'string', description: 'Created envelope ID' },
status: { type: 'string', description: 'Envelope status' },
statusDateTime: { type: 'string', description: 'Status change datetime', optional: true },
uri: { type: 'string', description: 'Envelope URI', optional: true },
},
}

View File

@@ -0,0 +1,259 @@
import type { OutputProperty, ToolResponse } from '@/tools/types'
/** Common envelope output properties */
export const ENVELOPE_OUTPUT_PROPERTIES = {
envelopeId: { type: 'string', description: 'Unique envelope identifier' },
status: {
type: 'string',
description: 'Envelope status (created, sent, delivered, completed, declined, voided)',
},
emailSubject: { type: 'string', description: 'Email subject line' },
sentDateTime: {
type: 'string',
description: 'ISO 8601 datetime when envelope was sent',
optional: true,
},
completedDateTime: {
type: 'string',
description: 'ISO 8601 datetime when envelope was completed',
optional: true,
},
createdDateTime: { type: 'string', description: 'ISO 8601 datetime when envelope was created' },
statusChangedDateTime: { type: 'string', description: 'ISO 8601 datetime of last status change' },
} as const satisfies Record<string, OutputProperty>
export const RECIPIENT_OUTPUT_PROPERTIES = {
recipientId: { type: 'string', description: 'Recipient identifier' },
name: { type: 'string', description: 'Recipient name' },
email: { type: 'string', description: 'Recipient email address' },
status: {
type: 'string',
description: 'Recipient signing status (sent, delivered, completed, declined)',
},
signedDateTime: {
type: 'string',
description: 'ISO 8601 datetime when recipient signed',
optional: true,
},
deliveredDateTime: {
type: 'string',
description: 'ISO 8601 datetime when delivered to recipient',
optional: true,
},
} as const satisfies Record<string, OutputProperty>
export const TEMPLATE_OUTPUT_PROPERTIES = {
templateId: { type: 'string', description: 'Template identifier' },
name: { type: 'string', description: 'Template name' },
description: { type: 'string', description: 'Template description', optional: true },
shared: { type: 'boolean', description: 'Whether template is shared', optional: true },
created: { type: 'string', description: 'ISO 8601 creation date' },
lastModified: { type: 'string', description: 'ISO 8601 last modified date' },
} as const satisfies Record<string, OutputProperty>
export const ENVELOPE_OBJECT_OUTPUT: OutputProperty = {
type: 'object',
description: 'DocuSign envelope',
properties: ENVELOPE_OUTPUT_PROPERTIES,
}
export const ENVELOPES_ARRAY_OUTPUT: OutputProperty = {
type: 'array',
description: 'Array of DocuSign envelopes',
items: {
type: 'object',
properties: ENVELOPE_OUTPUT_PROPERTIES,
},
}
export const RECIPIENT_OBJECT_OUTPUT: OutputProperty = {
type: 'object',
description: 'DocuSign recipient',
properties: RECIPIENT_OUTPUT_PROPERTIES,
}
export const RECIPIENTS_ARRAY_OUTPUT: OutputProperty = {
type: 'array',
description: 'Array of DocuSign recipients',
items: {
type: 'object',
properties: RECIPIENT_OUTPUT_PROPERTIES,
},
}
export const TEMPLATES_ARRAY_OUTPUT: OutputProperty = {
type: 'array',
description: 'Array of DocuSign templates',
items: {
type: 'object',
properties: TEMPLATE_OUTPUT_PROPERTIES,
},
}
/** Params interfaces */
export interface DocuSignSendEnvelopeParams {
accessToken: string
emailSubject: string
emailBody?: string
signerEmail: string
signerName: string
ccEmail?: string
ccName?: string
file?: unknown
status?: string
}
export interface DocuSignCreateFromTemplateParams {
accessToken: string
templateId: string
emailSubject?: string
emailBody?: string
templateRoles: string
status?: string
}
export interface DocuSignGetEnvelopeParams {
accessToken: string
envelopeId: string
}
export interface DocuSignListEnvelopesParams {
accessToken: string
fromDate?: string
toDate?: string
envelopeStatus?: string
searchText?: string
count?: string
}
export interface DocuSignVoidEnvelopeParams {
accessToken: string
envelopeId: string
voidedReason: string
}
export interface DocuSignDownloadDocumentParams {
accessToken: string
envelopeId: string
documentId?: string
}
export interface DocuSignListTemplatesParams {
accessToken: string
searchText?: string
count?: string
}
export interface DocuSignListRecipientsParams {
accessToken: string
envelopeId: string
}
/** Response interfaces */
export interface DocuSignSendEnvelopeResponse extends ToolResponse {
output: {
envelopeId: string
status: string
statusDateTime: string | null
uri: string | null
}
}
export interface DocuSignCreateFromTemplateResponse extends ToolResponse {
output: {
envelopeId: string
status: string
statusDateTime: string | null
uri: string | null
}
}
export interface DocuSignGetEnvelopeResponse extends ToolResponse {
output: {
envelopeId: string
status: string
emailSubject: string | null
sentDateTime: string | null
completedDateTime: string | null
createdDateTime: string | null
statusChangedDateTime: string | null
voidedReason: string | null
signerCount: number
documentCount: number
}
}
export interface DocuSignListEnvelopesResponse extends ToolResponse {
output: {
envelopes: Array<{
envelopeId: string
status: string
emailSubject: string | null
sentDateTime: string | null
completedDateTime: string | null
createdDateTime: string | null
statusChangedDateTime: string | null
}>
totalSetSize: number
resultSetSize: number
}
}
export interface DocuSignVoidEnvelopeResponse extends ToolResponse {
output: {
envelopeId: string
status: string
}
}
export interface DocuSignDownloadDocumentResponse extends ToolResponse {
output: {
base64Content: string
mimeType: string
fileName: string
}
}
export interface DocuSignListTemplatesResponse extends ToolResponse {
output: {
templates: Array<{
templateId: string
name: string
description: string | null
shared: boolean
created: string | null
lastModified: string | null
}>
totalSetSize: number
resultSetSize: number
}
}
export interface DocuSignListRecipientsResponse extends ToolResponse {
output: {
signers: Array<{
recipientId: string
name: string
email: string
status: string
signedDateTime: string | null
deliveredDateTime: string | null
}>
carbonCopies: Array<{
recipientId: string
name: string
email: string
status: string
}>
}
}
export type DocuSignResponse =
| DocuSignSendEnvelopeResponse
| DocuSignCreateFromTemplateResponse
| DocuSignGetEnvelopeResponse
| DocuSignListEnvelopesResponse
| DocuSignVoidEnvelopeResponse
| DocuSignDownloadDocumentResponse
| DocuSignListTemplatesResponse
| DocuSignListRecipientsResponse

View File

@@ -0,0 +1,72 @@
import type {
DocuSignVoidEnvelopeParams,
DocuSignVoidEnvelopeResponse,
} from '@/tools/docusign/types'
import type { ToolConfig } from '@/tools/types'
export const docusignVoidEnvelopeTool: ToolConfig<
DocuSignVoidEnvelopeParams,
DocuSignVoidEnvelopeResponse
> = {
id: 'docusign_void_envelope',
name: 'Void DocuSign Envelope',
description: 'Void (cancel) a sent DocuSign envelope that has not yet been completed',
version: '1.0.0',
oauth: {
required: true,
provider: 'docusign',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'DocuSign OAuth access token',
},
envelopeId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The envelope ID to void',
},
voidedReason: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Reason for voiding the envelope',
},
},
request: {
url: '/api/tools/docusign',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
accessToken: params.accessToken,
operation: 'void_envelope',
envelopeId: params.envelopeId,
voidedReason: params.voidedReason,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.success === false) {
throw new Error(data.error || 'Failed to void envelope')
}
return {
success: true,
output: {
envelopeId: data.envelopeId ?? null,
status: data.status ?? 'voided',
},
}
},
outputs: {
envelopeId: { type: 'string', description: 'Voided envelope ID' },
status: { type: 'string', description: 'Envelope status (voided)' },
},
}

View File

@@ -348,6 +348,16 @@ import {
discordUpdateMemberTool,
discordUpdateRoleTool,
} from '@/tools/discord'
import {
docusignCreateFromTemplateTool,
docusignDownloadDocumentTool,
docusignGetEnvelopeTool,
docusignListEnvelopesTool,
docusignListRecipientsTool,
docusignListTemplatesTool,
docusignSendEnvelopeTool,
docusignVoidEnvelopeTool,
} from '@/tools/docusign'
import {
dropboxCopyTool,
dropboxCreateFolderTool,
@@ -3806,6 +3816,14 @@ export const tools: Record<string, ToolConfig> = {
discord_execute_webhook: discordExecuteWebhookTool,
discord_get_webhook: discordGetWebhookTool,
discord_delete_webhook: discordDeleteWebhookTool,
docusign_create_from_template: docusignCreateFromTemplateTool,
docusign_download_document: docusignDownloadDocumentTool,
docusign_get_envelope: docusignGetEnvelopeTool,
docusign_list_envelopes: docusignListEnvelopesTool,
docusign_list_recipients: docusignListRecipientsTool,
docusign_list_templates: docusignListTemplatesTool,
docusign_send_envelope: docusignSendEnvelopeTool,
docusign_void_envelope: docusignVoidEnvelopeTool,
datadog_submit_metrics: datadogSubmitMetricsTool,
datadog_query_timeseries: datadogQueryTimeseriesTool,
datadog_create_event: datadogCreateEventTool,