@divyacon/piece-gmail 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.MIT-AP +24 -0
- package/README.md +8 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/actions/ai-get-message-action.d.ts +7 -0
- package/dist/lib/actions/ai-get-message-action.d.ts.map +1 -0
- package/dist/lib/actions/ai-get-message-action.js +24 -0
- package/dist/lib/actions/ai-get-message-action.js.map +1 -0
- package/dist/lib/actions/ai-reply-to-thread-action.d.ts +16 -0
- package/dist/lib/actions/ai-reply-to-thread-action.d.ts.map +1 -0
- package/dist/lib/actions/ai-reply-to-thread-action.js +24 -0
- package/dist/lib/actions/ai-reply-to-thread-action.js.map +1 -0
- package/dist/lib/actions/ai-search-email-action.d.ts +21 -0
- package/dist/lib/actions/ai-search-email-action.d.ts.map +1 -0
- package/dist/lib/actions/ai-search-email-action.js +24 -0
- package/dist/lib/actions/ai-search-email-action.js.map +1 -0
- package/dist/lib/actions/ai-send-email-action.d.ts +18 -0
- package/dist/lib/actions/ai-send-email-action.d.ts.map +1 -0
- package/dist/lib/actions/ai-send-email-action.js +24 -0
- package/dist/lib/actions/ai-send-email-action.js.map +1 -0
- package/dist/lib/actions/create-draft-action.d.ts +16 -0
- package/dist/lib/actions/create-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/create-draft-action.js +137 -0
- package/dist/lib/actions/create-draft-action.js.map +1 -0
- package/dist/lib/actions/create-draft-reply-action.d.ts +17 -0
- package/dist/lib/actions/create-draft-reply-action.d.ts.map +1 -0
- package/dist/lib/actions/create-draft-reply-action.js +269 -0
- package/dist/lib/actions/create-draft-reply-action.js.map +1 -0
- package/dist/lib/actions/delete-draft-action.d.ts +7 -0
- package/dist/lib/actions/delete-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/delete-draft-action.js +62 -0
- package/dist/lib/actions/delete-draft-action.js.map +1 -0
- package/dist/lib/actions/forward-message-action.d.ts +12 -0
- package/dist/lib/actions/forward-message-action.d.ts.map +1 -0
- package/dist/lib/actions/forward-message-action.js +139 -0
- package/dist/lib/actions/forward-message-action.js.map +1 -0
- package/dist/lib/actions/get-attachment-action.d.ts +9 -0
- package/dist/lib/actions/get-attachment-action.d.ts.map +1 -0
- package/dist/lib/actions/get-attachment-action.js +80 -0
- package/dist/lib/actions/get-attachment-action.js.map +1 -0
- package/dist/lib/actions/get-draft-action.d.ts +7 -0
- package/dist/lib/actions/get-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/get-draft-action.js +56 -0
- package/dist/lib/actions/get-draft-action.js.map +1 -0
- package/dist/lib/actions/get-label-action.d.ts +7 -0
- package/dist/lib/actions/get-label-action.d.ts.map +1 -0
- package/dist/lib/actions/get-label-action.js +55 -0
- package/dist/lib/actions/get-label-action.js.map +1 -0
- package/dist/lib/actions/get-mail-action.d.ts +7 -0
- package/dist/lib/actions/get-mail-action.d.ts.map +1 -0
- package/dist/lib/actions/get-mail-action.js +44 -0
- package/dist/lib/actions/get-mail-action.js.map +1 -0
- package/dist/lib/actions/get-profile-action.d.ts +5 -0
- package/dist/lib/actions/get-profile-action.d.ts.map +1 -0
- package/dist/lib/actions/get-profile-action.js +43 -0
- package/dist/lib/actions/get-profile-action.js.map +1 -0
- package/dist/lib/actions/get-thread-action.d.ts +9 -0
- package/dist/lib/actions/get-thread-action.d.ts.map +1 -0
- package/dist/lib/actions/get-thread-action.js +53 -0
- package/dist/lib/actions/get-thread-action.js.map +1 -0
- package/dist/lib/actions/list-drafts-action.d.ts +8 -0
- package/dist/lib/actions/list-drafts-action.d.ts.map +1 -0
- package/dist/lib/actions/list-drafts-action.js +62 -0
- package/dist/lib/actions/list-drafts-action.js.map +1 -0
- package/dist/lib/actions/list-history-action.d.ts +9 -0
- package/dist/lib/actions/list-history-action.d.ts.map +1 -0
- package/dist/lib/actions/list-history-action.js +74 -0
- package/dist/lib/actions/list-history-action.js.map +1 -0
- package/dist/lib/actions/list-labels-action.d.ts +5 -0
- package/dist/lib/actions/list-labels-action.d.ts.map +1 -0
- package/dist/lib/actions/list-labels-action.js +47 -0
- package/dist/lib/actions/list-labels-action.js.map +1 -0
- package/dist/lib/actions/list-threads-action.d.ts +10 -0
- package/dist/lib/actions/list-threads-action.d.ts.map +1 -0
- package/dist/lib/actions/list-threads-action.js +74 -0
- package/dist/lib/actions/list-threads-action.js.map +1 -0
- package/dist/lib/actions/reply-to-email-action.d.ts +16 -0
- package/dist/lib/actions/reply-to-email-action.d.ts.map +1 -0
- package/dist/lib/actions/reply-to-email-action.js +202 -0
- package/dist/lib/actions/reply-to-email-action.js.map +1 -0
- package/dist/lib/actions/request-approval-in-email.d.ts +15 -0
- package/dist/lib/actions/request-approval-in-email.d.ts.map +1 -0
- package/dist/lib/actions/request-approval-in-email.js +174 -0
- package/dist/lib/actions/request-approval-in-email.js.map +1 -0
- package/dist/lib/actions/search-email-action.d.ts +22 -0
- package/dist/lib/actions/search-email-action.d.ts.map +1 -0
- package/dist/lib/actions/search-email-action.js +179 -0
- package/dist/lib/actions/search-email-action.js.map +1 -0
- package/dist/lib/actions/send-draft-action.d.ts +7 -0
- package/dist/lib/actions/send-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/send-draft-action.js +57 -0
- package/dist/lib/actions/send-draft-action.js.map +1 -0
- package/dist/lib/actions/send-email-action.d.ts +18 -0
- package/dist/lib/actions/send-email-action.d.ts.map +1 -0
- package/dist/lib/actions/send-email-action.js +196 -0
- package/dist/lib/actions/send-email-action.js.map +1 -0
- package/dist/lib/actions/stop-watch-action.d.ts +5 -0
- package/dist/lib/actions/stop-watch-action.d.ts.map +1 -0
- package/dist/lib/actions/stop-watch-action.js +41 -0
- package/dist/lib/actions/stop-watch-action.js.map +1 -0
- package/dist/lib/actions/update-draft-action.d.ts +17 -0
- package/dist/lib/actions/update-draft-action.d.ts.map +1 -0
- package/dist/lib/actions/update-draft-action.js +193 -0
- package/dist/lib/actions/update-draft-action.js.map +1 -0
- package/dist/lib/auth.d.ts +12 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +108 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/common/data.d.ts +132 -0
- package/dist/lib/common/data.d.ts.map +1 -0
- package/dist/lib/common/data.js +209 -0
- package/dist/lib/common/data.js.map +1 -0
- package/dist/lib/common/mime.d.ts +29 -0
- package/dist/lib/common/mime.d.ts.map +1 -0
- package/dist/lib/common/mime.js +59 -0
- package/dist/lib/common/mime.js.map +1 -0
- package/dist/lib/common/models.d.ts +86 -0
- package/dist/lib/common/models.d.ts.map +1 -0
- package/dist/lib/common/models.js +26 -0
- package/dist/lib/common/models.js.map +1 -0
- package/dist/lib/common/props.d.ts +28 -0
- package/dist/lib/common/props.d.ts.map +1 -0
- package/dist/lib/common/props.js +231 -0
- package/dist/lib/common/props.js.map +1 -0
- package/dist/lib/output-schemas.d.ts +29 -0
- package/dist/lib/output-schemas.d.ts.map +1 -0
- package/dist/lib/output-schemas.js +1029 -0
- package/dist/lib/output-schemas.js.map +1 -0
- package/dist/lib/triggers/new-attachment.d.ts +144 -0
- package/dist/lib/triggers/new-attachment.d.ts.map +1 -0
- package/dist/lib/triggers/new-attachment.js +150 -0
- package/dist/lib/triggers/new-attachment.js.map +1 -0
- package/dist/lib/triggers/new-conversation.d.ts +59 -0
- package/dist/lib/triggers/new-conversation.d.ts.map +1 -0
- package/dist/lib/triggers/new-conversation.js +308 -0
- package/dist/lib/triggers/new-conversation.js.map +1 -0
- package/dist/lib/triggers/new-email.d.ts +52 -0
- package/dist/lib/triggers/new-email.d.ts.map +1 -0
- package/dist/lib/triggers/new-email.js +135 -0
- package/dist/lib/triggers/new-email.js.map +1 -0
- package/dist/lib/triggers/new-label.d.ts +15 -0
- package/dist/lib/triggers/new-label.d.ts.map +1 -0
- package/dist/lib/triggers/new-label.js +82 -0
- package/dist/lib/triggers/new-label.js.map +1 -0
- package/dist/lib/triggers/new-labeled-email.d.ts +35 -0
- package/dist/lib/triggers/new-labeled-email.d.ts.map +1 -0
- package/dist/lib/triggers/new-labeled-email.js +147 -0
- package/dist/lib/triggers/new-labeled-email.js.map +1 -0
- package/package.json +54 -4
- package/src/index.ts +107 -0
- package/src/lib/actions/ai-get-message-action.ts +20 -0
- package/src/lib/actions/ai-reply-to-thread-action.ts +20 -0
- package/src/lib/actions/ai-search-email-action.ts +21 -0
- package/src/lib/actions/ai-send-email-action.ts +20 -0
- package/src/lib/actions/create-draft-action.ts +148 -0
- package/src/lib/actions/create-draft-reply-action.ts +314 -0
- package/src/lib/actions/delete-draft-action.ts +57 -0
- package/src/lib/actions/forward-message-action.ts +159 -0
- package/src/lib/actions/get-attachment-action.ts +83 -0
- package/src/lib/actions/get-draft-action.ts +53 -0
- package/src/lib/actions/get-label-action.ts +53 -0
- package/src/lib/actions/get-mail-action.ts +52 -0
- package/src/lib/actions/get-profile-action.ts +39 -0
- package/src/lib/actions/get-thread-action.ts +47 -0
- package/src/lib/actions/list-drafts-action.ts +72 -0
- package/src/lib/actions/list-history-action.ts +83 -0
- package/src/lib/actions/list-labels-action.ts +43 -0
- package/src/lib/actions/list-threads-action.ts +89 -0
- package/src/lib/actions/reply-to-email-action.ts +228 -0
- package/src/lib/actions/request-approval-in-email.ts +197 -0
- package/src/lib/actions/search-email-action.ts +219 -0
- package/src/lib/actions/send-draft-action.ts +54 -0
- package/src/lib/actions/send-email-action.ts +213 -0
- package/src/lib/actions/stop-watch-action.ts +37 -0
- package/src/lib/actions/update-draft-action.ts +211 -0
- package/src/lib/auth.ts +116 -0
- package/src/lib/common/data.ts +268 -0
- package/src/lib/common/mime.ts +82 -0
- package/src/lib/common/models.ts +91 -0
- package/src/lib/common/props.ts +262 -0
- package/src/lib/output-schemas.ts +1061 -0
- package/src/lib/triggers/new-attachment.ts +203 -0
- package/src/lib/triggers/new-conversation.ts +413 -0
- package/src/lib/triggers/new-email.ts +173 -0
- package/src/lib/triggers/new-label.ts +83 -0
- package/src/lib/triggers/new-labeled-email.ts +197 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { createAction, Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import mime from 'mime-types';
|
|
3
|
+
import MailComposer from 'nodemailer/lib/mail-composer';
|
|
4
|
+
import Mail, { Attachment } from 'nodemailer/lib/mailer';
|
|
5
|
+
import { gmailAuth, createGoogleClient, getUserEmail } from '../auth';
|
|
6
|
+
import { gmail as googleGmail } from '@googleapis/gmail';
|
|
7
|
+
import { GmailProps } from '../common/props';
|
|
8
|
+
import { replyToEmailActionOutputSchema } from '../output-schemas';
|
|
9
|
+
|
|
10
|
+
export const gmailReplyToEmailAction = createAction({
|
|
11
|
+
auth: gmailAuth,
|
|
12
|
+
name: 'reply_to_email',
|
|
13
|
+
displayName: 'Reply to Email',
|
|
14
|
+
description: 'Reply to an existing email.',
|
|
15
|
+
audience: 'human',
|
|
16
|
+
aiMetadata: {
|
|
17
|
+
description:
|
|
18
|
+
'Sends a reply to an existing email, preserving the thread and subject and addressing the original sender (reply) or all participants (reply all). Use this to respond within a known conversation; requires the Gmail message ID of the email being answered. Not idempotent: each call sends a new reply message into the thread.',
|
|
19
|
+
idempotent: false,
|
|
20
|
+
},
|
|
21
|
+
props: {
|
|
22
|
+
message_id: GmailProps.message,
|
|
23
|
+
reply_type: Property.StaticDropdown({
|
|
24
|
+
displayName: 'Reply Type',
|
|
25
|
+
description:
|
|
26
|
+
'Choose whether to reply to sender only or to all recipients',
|
|
27
|
+
required: true,
|
|
28
|
+
defaultValue: 'reply',
|
|
29
|
+
options: {
|
|
30
|
+
disabled: false,
|
|
31
|
+
options: [
|
|
32
|
+
{
|
|
33
|
+
label: 'Reply (to sender only)',
|
|
34
|
+
value: 'reply',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 'Reply All (to all recipients)',
|
|
38
|
+
value: 'reply_all',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
body_type: Property.StaticDropdown({
|
|
44
|
+
displayName: 'Body Type',
|
|
45
|
+
required: true,
|
|
46
|
+
defaultValue: 'plain_text',
|
|
47
|
+
options: {
|
|
48
|
+
disabled: false,
|
|
49
|
+
options: [
|
|
50
|
+
{
|
|
51
|
+
label: 'Plain text',
|
|
52
|
+
value: 'plain_text',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: 'HTML',
|
|
56
|
+
value: 'html',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
body: Property.LongText({
|
|
62
|
+
displayName: 'Reply Body',
|
|
63
|
+
description: 'Your reply message content',
|
|
64
|
+
required: true,
|
|
65
|
+
}),
|
|
66
|
+
sender_name: Property.ShortText({
|
|
67
|
+
displayName: 'Sender Name',
|
|
68
|
+
description: 'Optional sender name to display',
|
|
69
|
+
required: false,
|
|
70
|
+
}),
|
|
71
|
+
attachment: Property.File({
|
|
72
|
+
displayName: 'Attachment',
|
|
73
|
+
description: 'Optional file to attach to your reply',
|
|
74
|
+
required: false,
|
|
75
|
+
}),
|
|
76
|
+
attachment_name: Property.ShortText({
|
|
77
|
+
displayName: 'Attachment Name',
|
|
78
|
+
description: 'Custom name for the attachment',
|
|
79
|
+
required: false,
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
outputSchema: replyToEmailActionOutputSchema,
|
|
83
|
+
async run(context) {
|
|
84
|
+
const authClient = await createGoogleClient(context.auth);
|
|
85
|
+
|
|
86
|
+
const gmail = googleGmail({ version: 'v1', auth: authClient });
|
|
87
|
+
|
|
88
|
+
const originalMessage = await gmail.users.messages.get({
|
|
89
|
+
userId: 'me',
|
|
90
|
+
id: context.propsValue.message_id,
|
|
91
|
+
format: 'full',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!originalMessage.data || !originalMessage.data.payload) {
|
|
95
|
+
throw new Error('Could not fetch original message details');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const headers = originalMessage.data.payload.headers || [];
|
|
99
|
+
const headerMap = headers.reduce(
|
|
100
|
+
(acc: { [key: string]: string }, header) => {
|
|
101
|
+
if (header.name && header.value) {
|
|
102
|
+
acc[header.name.toLowerCase()] = header.value;
|
|
103
|
+
}
|
|
104
|
+
return acc;
|
|
105
|
+
},
|
|
106
|
+
{}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const originalSubject = headerMap['subject'] || '';
|
|
110
|
+
const originalFrom = headerMap['from'] || '';
|
|
111
|
+
const originalTo = headerMap['to'] || '';
|
|
112
|
+
const originalCc = headerMap['cc'] || '';
|
|
113
|
+
const originalReplyTo = headerMap['reply-to'] || '';
|
|
114
|
+
const originalMessageId = headerMap['message-id'] || '';
|
|
115
|
+
const originalReferences = headerMap['references'] || '';
|
|
116
|
+
|
|
117
|
+
const toRecipients: string[] = [];
|
|
118
|
+
const ccRecipients: string[] = [];
|
|
119
|
+
|
|
120
|
+
if (context.propsValue.reply_type === 'reply_all') {
|
|
121
|
+
const senderEmail = originalReplyTo || originalFrom;
|
|
122
|
+
if (senderEmail) {
|
|
123
|
+
toRecipients.push(senderEmail);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const currentUserEmail = await getUserEmail(context.auth, authClient);
|
|
127
|
+
|
|
128
|
+
if (originalTo) {
|
|
129
|
+
const toEmails = originalTo.split(',').map((email) => email.trim());
|
|
130
|
+
toRecipients.push(
|
|
131
|
+
...toEmails.filter((email) => !email.includes(currentUserEmail || ''))
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (originalCc) {
|
|
136
|
+
const ccEmails = originalCc.split(',').map((email) => email.trim());
|
|
137
|
+
ccRecipients.push(
|
|
138
|
+
...ccEmails.filter((email) => !email.includes(currentUserEmail || ''))
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
const senderEmail = originalReplyTo || originalFrom;
|
|
143
|
+
if (senderEmail) {
|
|
144
|
+
toRecipients.push(senderEmail);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let replySubject = originalSubject;
|
|
149
|
+
if (!replySubject.toLowerCase().startsWith('re:')) {
|
|
150
|
+
replySubject = `Re: ${replySubject}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let referencesHeader = originalMessageId;
|
|
154
|
+
if (originalReferences) {
|
|
155
|
+
referencesHeader = `${originalReferences} ${originalMessageId}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const senderEmail = await getUserEmail(context.auth, authClient);
|
|
159
|
+
|
|
160
|
+
const subjectBase64 = Buffer.from(replySubject).toString('base64');
|
|
161
|
+
const mailOptions: Mail.Options = {
|
|
162
|
+
to: toRecipients.join(', '),
|
|
163
|
+
cc: ccRecipients.length > 0 ? ccRecipients.join(', ') : undefined,
|
|
164
|
+
subject: `=?UTF-8?B?${subjectBase64}?=`,
|
|
165
|
+
text:
|
|
166
|
+
context.propsValue.body_type === 'plain_text'
|
|
167
|
+
? context.propsValue.body
|
|
168
|
+
: undefined,
|
|
169
|
+
html:
|
|
170
|
+
context.propsValue.body_type === 'html'
|
|
171
|
+
? context.propsValue.body
|
|
172
|
+
: undefined,
|
|
173
|
+
attachments: [],
|
|
174
|
+
headers: [
|
|
175
|
+
{
|
|
176
|
+
key: 'In-Reply-To',
|
|
177
|
+
value: originalMessageId,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
key: 'References',
|
|
181
|
+
value: referencesHeader,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
if (senderEmail) {
|
|
187
|
+
mailOptions.from = context.propsValue.sender_name
|
|
188
|
+
? `${context.propsValue.sender_name} <${senderEmail}>`
|
|
189
|
+
: senderEmail;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (context.propsValue.attachment) {
|
|
193
|
+
const lookupResult = mime.lookup(
|
|
194
|
+
context.propsValue.attachment.extension || ''
|
|
195
|
+
);
|
|
196
|
+
const attachmentOption: Attachment[] = [
|
|
197
|
+
{
|
|
198
|
+
filename:
|
|
199
|
+
context.propsValue.attachment_name ??
|
|
200
|
+
context.propsValue.attachment.filename,
|
|
201
|
+
content: context.propsValue.attachment.base64,
|
|
202
|
+
contentType: lookupResult || undefined,
|
|
203
|
+
encoding: 'base64',
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
mailOptions.attachments = attachmentOption;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const mail: any = new MailComposer(mailOptions).compile();
|
|
210
|
+
mail.keepBcc = true;
|
|
211
|
+
const mailBody = await mail.build();
|
|
212
|
+
|
|
213
|
+
const encodedPayload = Buffer.from(mailBody)
|
|
214
|
+
.toString('base64')
|
|
215
|
+
.replace(/\+/g, '-')
|
|
216
|
+
.replace(/\//g, '_');
|
|
217
|
+
|
|
218
|
+
const response = await gmail.users.messages.send({
|
|
219
|
+
userId: 'me',
|
|
220
|
+
requestBody: {
|
|
221
|
+
threadId: originalMessage.data.threadId || undefined,
|
|
222
|
+
raw: encodedPayload,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return response.data;
|
|
227
|
+
},
|
|
228
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { createAction, Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import {
|
|
3
|
+
gmailAuth,
|
|
4
|
+
createGoogleClient,
|
|
5
|
+
getAccessToken,
|
|
6
|
+
getUserEmail,
|
|
7
|
+
} from '../auth';
|
|
8
|
+
import { gmail as googleGmail } from '@googleapis/gmail';
|
|
9
|
+
import MailComposer from 'nodemailer/lib/mail-composer';
|
|
10
|
+
import Mail from 'nodemailer/lib/mailer';
|
|
11
|
+
import { assertNotNullOrUndefined } from '@agnocon/pieces-framework';
|
|
12
|
+
import { ExecutionType } from '@agnocon/pieces-framework';
|
|
13
|
+
import { requestApprovalInMailActionOutputSchema } from '../output-schemas';
|
|
14
|
+
|
|
15
|
+
export const requestApprovalInEmail = createAction({
|
|
16
|
+
auth: gmailAuth,
|
|
17
|
+
name: 'request_approval_in_mail',
|
|
18
|
+
displayName: 'Request Approval in Email',
|
|
19
|
+
description:
|
|
20
|
+
'Send approval request email and then wait until the email is approved or disapproved',
|
|
21
|
+
audience: 'both',
|
|
22
|
+
aiMetadata: {
|
|
23
|
+
description:
|
|
24
|
+
'Sends an email with a single link to a confirmation page where the recipient chooses Approve or Disapprove, then pauses the flow until they respond, resuming with their decision. Use this as a human-in-the-loop gate before proceeding with a sensitive action. The flow blocks indefinitely until a response arrives. Not idempotent: each call sends a new approval email and creates a new wait.',
|
|
25
|
+
idempotent: false,
|
|
26
|
+
},
|
|
27
|
+
props: {
|
|
28
|
+
receiver: Property.ShortText({
|
|
29
|
+
displayName: 'Receiver Email (To)',
|
|
30
|
+
description:
|
|
31
|
+
'The email address of the recipient who will receive the approval request.',
|
|
32
|
+
required: true,
|
|
33
|
+
}),
|
|
34
|
+
|
|
35
|
+
cc: Property.Array({
|
|
36
|
+
displayName: 'CC Email',
|
|
37
|
+
description:
|
|
38
|
+
'The email addresses of the recipients who will receive a carbon copy of the approval request.',
|
|
39
|
+
required: false,
|
|
40
|
+
}),
|
|
41
|
+
bcc: Property.Array({
|
|
42
|
+
displayName: 'BCC Email',
|
|
43
|
+
description:
|
|
44
|
+
'The email addresses of the recipients who will receive a blind carbon copy of the approval request.',
|
|
45
|
+
required: false,
|
|
46
|
+
}),
|
|
47
|
+
subject: Property.ShortText({
|
|
48
|
+
displayName: 'Subject',
|
|
49
|
+
description: 'The subject of the approval request email.',
|
|
50
|
+
required: true,
|
|
51
|
+
}),
|
|
52
|
+
body: Property.ShortText({
|
|
53
|
+
displayName: 'Body',
|
|
54
|
+
description: 'Body for the email you want to send',
|
|
55
|
+
required: true,
|
|
56
|
+
}),
|
|
57
|
+
reply_to: Property.Array({
|
|
58
|
+
displayName: 'Reply-To Email',
|
|
59
|
+
description: 'Email address to set as the "Reply-To" header',
|
|
60
|
+
required: false,
|
|
61
|
+
}),
|
|
62
|
+
sender_name: Property.ShortText({
|
|
63
|
+
displayName: 'Sender Name',
|
|
64
|
+
required: false,
|
|
65
|
+
}),
|
|
66
|
+
from: Property.ShortText({
|
|
67
|
+
displayName: 'Sender Email',
|
|
68
|
+
description:
|
|
69
|
+
"The address must be listed in your GMail account's settings",
|
|
70
|
+
required: false,
|
|
71
|
+
}),
|
|
72
|
+
in_reply_to: Property.ShortText({
|
|
73
|
+
displayName: 'In reply to',
|
|
74
|
+
description: 'Reply to this Message-ID',
|
|
75
|
+
required: false,
|
|
76
|
+
}),
|
|
77
|
+
},
|
|
78
|
+
outputSchema: requestApprovalInMailActionOutputSchema,
|
|
79
|
+
async run(context) {
|
|
80
|
+
if (context.executionType === ExecutionType.BEGIN) {
|
|
81
|
+
try {
|
|
82
|
+
const token = await getAccessToken(context.auth);
|
|
83
|
+
|
|
84
|
+
const { subject, body } = context.propsValue;
|
|
85
|
+
|
|
86
|
+
assertNotNullOrUndefined(token, 'token');
|
|
87
|
+
assertNotNullOrUndefined(context.propsValue.receiver, 'receiver');
|
|
88
|
+
assertNotNullOrUndefined(subject, 'subject');
|
|
89
|
+
assertNotNullOrUndefined(body, 'body');
|
|
90
|
+
|
|
91
|
+
const waitpoint = await context.run.createWaitpoint({
|
|
92
|
+
type: 'WEBHOOK',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const confirmationLink = `${waitpoint.resumeUrl}/confirm`;
|
|
96
|
+
|
|
97
|
+
const htmlBody = `
|
|
98
|
+
<div>
|
|
99
|
+
<p>${body}</p>
|
|
100
|
+
<br />
|
|
101
|
+
<p>
|
|
102
|
+
<a href="${confirmationLink}" style="display: inline-block; padding: 10px 20px; background-color: #6e41e2; color: white; text-decoration: none; border-radius: 4px;">Review & Respond</a>
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
const authClient = await createGoogleClient(context.auth);
|
|
108
|
+
|
|
109
|
+
const gmail = googleGmail({ version: 'v1', auth: authClient });
|
|
110
|
+
|
|
111
|
+
const subjectBase64 = Buffer.from(
|
|
112
|
+
context.propsValue['subject']
|
|
113
|
+
).toString('base64');
|
|
114
|
+
|
|
115
|
+
const replyTo = context.propsValue['reply_to']?.filter(
|
|
116
|
+
(email) => email !== ''
|
|
117
|
+
);
|
|
118
|
+
const receiverEmail = context.propsValue.receiver;
|
|
119
|
+
const cc = context.propsValue['cc']?.filter((email) => email !== '');
|
|
120
|
+
const bcc = context.propsValue['bcc']?.filter((email) => email !== '');
|
|
121
|
+
const mailOptions: Mail.Options = {
|
|
122
|
+
to: receiverEmail,
|
|
123
|
+
cc: cc ? cc.join(', ') : undefined,
|
|
124
|
+
bcc: bcc ? bcc.join(', ') : undefined,
|
|
125
|
+
subject: `=?UTF-8?B?${subjectBase64}?=`,
|
|
126
|
+
replyTo: replyTo ? replyTo.join(', ') : '',
|
|
127
|
+
// text:
|
|
128
|
+
// context.propsValue.body_type === 'plain_text'
|
|
129
|
+
// ? context.propsValue['body']
|
|
130
|
+
// : undefined,
|
|
131
|
+
html: htmlBody,
|
|
132
|
+
attachments: [],
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const senderEmail =
|
|
136
|
+
context.propsValue.from ||
|
|
137
|
+
(await getUserEmail(context.auth, authClient));
|
|
138
|
+
if (senderEmail) {
|
|
139
|
+
mailOptions.from = context.propsValue.sender_name
|
|
140
|
+
? `${context.propsValue['sender_name']} <${senderEmail}>`
|
|
141
|
+
: senderEmail;
|
|
142
|
+
}
|
|
143
|
+
let threadId = undefined;
|
|
144
|
+
if (context.propsValue.in_reply_to) {
|
|
145
|
+
mailOptions.headers = [
|
|
146
|
+
{
|
|
147
|
+
key: 'References',
|
|
148
|
+
value: context.propsValue.in_reply_to,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
key: 'In-Reply-To',
|
|
152
|
+
value: context.propsValue.in_reply_to,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
const messages = await gmail.users.messages.list({
|
|
156
|
+
userId: 'me',
|
|
157
|
+
q: `Rfc822msgid:${context.propsValue.in_reply_to}`,
|
|
158
|
+
});
|
|
159
|
+
threadId = messages.data.messages?.[0].threadId;
|
|
160
|
+
}
|
|
161
|
+
const mail: any = new MailComposer(mailOptions).compile();
|
|
162
|
+
mail.keepBcc = true;
|
|
163
|
+
const mailBody = await mail.build();
|
|
164
|
+
|
|
165
|
+
const encodedPayload = Buffer.from(mailBody)
|
|
166
|
+
.toString('base64')
|
|
167
|
+
.replace(/\+/g, '-')
|
|
168
|
+
.replace(/\//g, '_');
|
|
169
|
+
await gmail.users.messages.send({
|
|
170
|
+
userId: 'me',
|
|
171
|
+
requestBody: {
|
|
172
|
+
threadId,
|
|
173
|
+
raw: encodedPayload,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
context.run.waitForWaitpoint(waitpoint.id);
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
approved: false, // default approval is false
|
|
180
|
+
};
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error(
|
|
183
|
+
'[RequestApprovalEmail] Error during BEGIN execution:',
|
|
184
|
+
error
|
|
185
|
+
);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
const action = context.resumePayload.queryParams['action'];
|
|
190
|
+
const approved = action === 'approve';
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
approved,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { createAction, Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import { gmailAuth, createGoogleClient } from '../auth';
|
|
3
|
+
import { gmail as googleGmail } from '@googleapis/gmail';
|
|
4
|
+
import { convertAttachment, parseStream } from '../common/data';
|
|
5
|
+
import { GmailProps } from '../common/props';
|
|
6
|
+
import { GmailLabel } from '../common/models';
|
|
7
|
+
import { gmailSearchMailActionOutputSchema } from '../output-schemas';
|
|
8
|
+
|
|
9
|
+
export const gmailSearchMailAction = createAction({
|
|
10
|
+
auth: gmailAuth,
|
|
11
|
+
name: 'gmail_search_mail',
|
|
12
|
+
displayName: 'Find Email',
|
|
13
|
+
description:
|
|
14
|
+
'Find emails using advanced search criteria. If no filters are provided, the latest emails are returned.',
|
|
15
|
+
audience: 'human',
|
|
16
|
+
aiMetadata: {
|
|
17
|
+
description:
|
|
18
|
+
'Searches the mailbox for emails matching combinable filters (sender, recipient, subject, body text, label, category, date range, attachment presence/name) and returns the matched messages with parsed contents. Use this to locate messages or discover their IDs before reading or replying; with no filters it returns the most recent emails. Bound results with Max Results (1-500, default 10). Idempotent: a read-only search that does not modify the mailbox.',
|
|
19
|
+
idempotent: true,
|
|
20
|
+
},
|
|
21
|
+
props: {
|
|
22
|
+
from: GmailProps.from,
|
|
23
|
+
to: GmailProps.to,
|
|
24
|
+
subject: GmailProps.subject,
|
|
25
|
+
content: Property.ShortText({
|
|
26
|
+
displayName: 'Email Content',
|
|
27
|
+
description: 'Search for specific text within email body',
|
|
28
|
+
required: false,
|
|
29
|
+
}),
|
|
30
|
+
has_attachment: Property.Checkbox({
|
|
31
|
+
displayName: 'Has Attachment',
|
|
32
|
+
description: 'Only find emails with attachments',
|
|
33
|
+
required: false,
|
|
34
|
+
defaultValue: false,
|
|
35
|
+
}),
|
|
36
|
+
attachment_name: Property.ShortText({
|
|
37
|
+
displayName: 'Attachment Name',
|
|
38
|
+
description: 'Search for emails with specific attachment filename',
|
|
39
|
+
required: false,
|
|
40
|
+
}),
|
|
41
|
+
label: GmailProps.label({ required: false }),
|
|
42
|
+
category: GmailProps.category,
|
|
43
|
+
after_date: Property.DateTime({
|
|
44
|
+
displayName: 'After Date',
|
|
45
|
+
description: 'Find emails sent after this date',
|
|
46
|
+
required: false,
|
|
47
|
+
}),
|
|
48
|
+
before_date: Property.DateTime({
|
|
49
|
+
displayName: 'Before Date',
|
|
50
|
+
description: 'Find emails sent before this date',
|
|
51
|
+
required: false,
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
include_spam_trash: Property.Checkbox({
|
|
55
|
+
displayName: 'Include Spam & Trash',
|
|
56
|
+
description:
|
|
57
|
+
'Include emails from Spam and Trash folders in search results',
|
|
58
|
+
required: false,
|
|
59
|
+
defaultValue: false,
|
|
60
|
+
}),
|
|
61
|
+
max_results: Property.Number({
|
|
62
|
+
displayName: 'Max Results',
|
|
63
|
+
description: 'Maximum number of emails to return (1-500)',
|
|
64
|
+
required: false,
|
|
65
|
+
defaultValue: 10,
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
outputSchema: gmailSearchMailActionOutputSchema,
|
|
69
|
+
async run(context) {
|
|
70
|
+
const authClient = await createGoogleClient(context.auth);
|
|
71
|
+
|
|
72
|
+
const gmail = googleGmail({ version: 'v1', auth: authClient });
|
|
73
|
+
|
|
74
|
+
const queryParts: string[] = [];
|
|
75
|
+
|
|
76
|
+
if (context.propsValue.from?.trim()) {
|
|
77
|
+
queryParts.push(`from:(${context.propsValue.from.trim()})`);
|
|
78
|
+
}
|
|
79
|
+
if (context.propsValue.to?.trim()) {
|
|
80
|
+
queryParts.push(`to:(${context.propsValue.to.trim()})`);
|
|
81
|
+
}
|
|
82
|
+
if (context.propsValue.subject?.trim()) {
|
|
83
|
+
queryParts.push(`subject:(${context.propsValue.subject.trim()})`);
|
|
84
|
+
}
|
|
85
|
+
if (context.propsValue.content?.trim()) {
|
|
86
|
+
queryParts.push(`"${context.propsValue.content.trim()}"`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (context.propsValue.has_attachment) {
|
|
90
|
+
queryParts.push('has:attachment');
|
|
91
|
+
}
|
|
92
|
+
if (context.propsValue.attachment_name?.trim()) {
|
|
93
|
+
queryParts.push(
|
|
94
|
+
`filename:(${context.propsValue.attachment_name.trim()})`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (context.propsValue.label) {
|
|
99
|
+
const label = context.propsValue.label as GmailLabel;
|
|
100
|
+
queryParts.push(`label:${label.name}`);
|
|
101
|
+
}
|
|
102
|
+
if (context.propsValue.category?.trim()) {
|
|
103
|
+
queryParts.push(`category:${context.propsValue.category.trim()}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (context.propsValue.after_date) {
|
|
107
|
+
const afterDate = new Date(context.propsValue.after_date);
|
|
108
|
+
const afterDateStr = afterDate
|
|
109
|
+
.toISOString()
|
|
110
|
+
.split('T')[0]
|
|
111
|
+
.replace(/-/g, '/');
|
|
112
|
+
queryParts.push(`after:${afterDateStr}`);
|
|
113
|
+
}
|
|
114
|
+
if (context.propsValue.before_date) {
|
|
115
|
+
const beforeDate = new Date(context.propsValue.before_date);
|
|
116
|
+
const beforeDateStr = beforeDate
|
|
117
|
+
.toISOString()
|
|
118
|
+
.split('T')[0]
|
|
119
|
+
.replace(/-/g, '/');
|
|
120
|
+
queryParts.push(`before:${beforeDateStr}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const searchQuery = queryParts.join(' ');
|
|
124
|
+
|
|
125
|
+
const maxResults = Math.min(
|
|
126
|
+
Math.max(context.propsValue.max_results || 10, 1),
|
|
127
|
+
500
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const searchResponse = await gmail.users.messages.list({
|
|
132
|
+
userId: 'me',
|
|
133
|
+
...(searchQuery.trim() ? { q: searchQuery } : {}),
|
|
134
|
+
maxResults: maxResults,
|
|
135
|
+
includeSpamTrash: context.propsValue.include_spam_trash,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const messages = searchResponse.data.messages || [];
|
|
139
|
+
|
|
140
|
+
if (messages.length === 0) {
|
|
141
|
+
return {
|
|
142
|
+
found: false,
|
|
143
|
+
results: {
|
|
144
|
+
messages: [],
|
|
145
|
+
count: 0,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const detailedMessages = await Promise.all(
|
|
151
|
+
messages.map(async (message) => {
|
|
152
|
+
try {
|
|
153
|
+
const rawMailResponse = await gmail.users.messages.get({
|
|
154
|
+
userId: 'me',
|
|
155
|
+
id: message.id!,
|
|
156
|
+
format: 'raw',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const parsedMailResponse = await parseStream(
|
|
160
|
+
Buffer.from(
|
|
161
|
+
rawMailResponse.data.raw as string,
|
|
162
|
+
'base64'
|
|
163
|
+
).toString('utf-8')
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
id: message.id,
|
|
168
|
+
...parsedMailResponse,
|
|
169
|
+
attachments: await convertAttachment(
|
|
170
|
+
parsedMailResponse.attachments,
|
|
171
|
+
context.files
|
|
172
|
+
),
|
|
173
|
+
};
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error(
|
|
176
|
+
`Failed to get details for message ${message.id}:`,
|
|
177
|
+
error
|
|
178
|
+
);
|
|
179
|
+
return {
|
|
180
|
+
id: message.id,
|
|
181
|
+
threadId: message.threadId,
|
|
182
|
+
error: 'Failed to retrieve message details',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
found: true,
|
|
190
|
+
results: {
|
|
191
|
+
messages: detailedMessages,
|
|
192
|
+
count: detailedMessages.length,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
} catch (error: any) {
|
|
196
|
+
// Enhanced error handling
|
|
197
|
+
if (error.code === 400) {
|
|
198
|
+
if (error.message?.includes('Invalid query')) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Invalid search query: "${searchQuery}". Please check your search syntax.`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`Invalid search request: ${error.message}`);
|
|
204
|
+
} else if (error.code === 403) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
'Insufficient permissions to search emails. Ensure the gmail.readonly scope is granted.'
|
|
207
|
+
);
|
|
208
|
+
} else if (error.code === 429) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
'Gmail API rate limit exceeded. Please try again later.'
|
|
211
|
+
);
|
|
212
|
+
} else if (error.code === 500) {
|
|
213
|
+
throw new Error('Gmail API server error. Please try again later.');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
throw new Error(`Failed to search emails: ${error.message}`);
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createAction, Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import { gmailAuth, createGoogleClient } from '../auth';
|
|
3
|
+
import { gmail as googleGmail } from '@googleapis/gmail';
|
|
4
|
+
import { gmailSendDraftActionOutputSchema } from '../output-schemas';
|
|
5
|
+
|
|
6
|
+
export const gmailSendDraftAction = createAction({
|
|
7
|
+
auth: gmailAuth,
|
|
8
|
+
name: 'gmail_send_draft',
|
|
9
|
+
displayName: 'Send Draft',
|
|
10
|
+
description: 'Send an existing draft email by its ID.',
|
|
11
|
+
audience: 'ai',
|
|
12
|
+
aiMetadata: {
|
|
13
|
+
description:
|
|
14
|
+
'Sends an existing draft email identified by its draft ID, delivering it to its recipients. Use this after creating or editing a draft with Create Draft / Update Draft; obtain the draft ID from List Drafts. Not idempotent: each call sends the draft as a message.',
|
|
15
|
+
idempotent: false,
|
|
16
|
+
},
|
|
17
|
+
props: {
|
|
18
|
+
draft_id: Property.ShortText({
|
|
19
|
+
displayName: 'Draft ID',
|
|
20
|
+
description: 'The ID of the draft to send (obtain from List Drafts).',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
outputSchema: gmailSendDraftActionOutputSchema,
|
|
25
|
+
async run(context) {
|
|
26
|
+
const authClient = await createGoogleClient(context.auth);
|
|
27
|
+
const gmail = googleGmail({ version: 'v1', auth: authClient });
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const response = await gmail.users.drafts.send({
|
|
31
|
+
userId: 'me',
|
|
32
|
+
requestBody: {
|
|
33
|
+
id: context.propsValue.draft_id,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return response.data;
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
if (error.code === 403) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
'Insufficient permissions to send a draft. Ensure the gmail.send scope is granted.'
|
|
41
|
+
);
|
|
42
|
+
} else if (error.code === 404) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Draft not found: "${context.propsValue.draft_id}". Use List Drafts to find a valid draft ID.`
|
|
45
|
+
);
|
|
46
|
+
} else if (error.code === 429) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'Gmail API rate limit exceeded. Please try again later.'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`Failed to send draft: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|