@divyacon/piece-gmail 0.0.0-stage → 0.1.0

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.
Files changed (188) hide show
  1. package/LICENSE.MIT-AP +24 -0
  2. package/README.md +8 -2
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +111 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/actions/ai-get-message-action.d.ts +7 -0
  8. package/dist/lib/actions/ai-get-message-action.d.ts.map +1 -0
  9. package/dist/lib/actions/ai-get-message-action.js +24 -0
  10. package/dist/lib/actions/ai-get-message-action.js.map +1 -0
  11. package/dist/lib/actions/ai-reply-to-thread-action.d.ts +16 -0
  12. package/dist/lib/actions/ai-reply-to-thread-action.d.ts.map +1 -0
  13. package/dist/lib/actions/ai-reply-to-thread-action.js +24 -0
  14. package/dist/lib/actions/ai-reply-to-thread-action.js.map +1 -0
  15. package/dist/lib/actions/ai-search-email-action.d.ts +21 -0
  16. package/dist/lib/actions/ai-search-email-action.d.ts.map +1 -0
  17. package/dist/lib/actions/ai-search-email-action.js +24 -0
  18. package/dist/lib/actions/ai-search-email-action.js.map +1 -0
  19. package/dist/lib/actions/ai-send-email-action.d.ts +18 -0
  20. package/dist/lib/actions/ai-send-email-action.d.ts.map +1 -0
  21. package/dist/lib/actions/ai-send-email-action.js +24 -0
  22. package/dist/lib/actions/ai-send-email-action.js.map +1 -0
  23. package/dist/lib/actions/create-draft-action.d.ts +16 -0
  24. package/dist/lib/actions/create-draft-action.d.ts.map +1 -0
  25. package/dist/lib/actions/create-draft-action.js +137 -0
  26. package/dist/lib/actions/create-draft-action.js.map +1 -0
  27. package/dist/lib/actions/create-draft-reply-action.d.ts +17 -0
  28. package/dist/lib/actions/create-draft-reply-action.d.ts.map +1 -0
  29. package/dist/lib/actions/create-draft-reply-action.js +269 -0
  30. package/dist/lib/actions/create-draft-reply-action.js.map +1 -0
  31. package/dist/lib/actions/delete-draft-action.d.ts +7 -0
  32. package/dist/lib/actions/delete-draft-action.d.ts.map +1 -0
  33. package/dist/lib/actions/delete-draft-action.js +62 -0
  34. package/dist/lib/actions/delete-draft-action.js.map +1 -0
  35. package/dist/lib/actions/forward-message-action.d.ts +12 -0
  36. package/dist/lib/actions/forward-message-action.d.ts.map +1 -0
  37. package/dist/lib/actions/forward-message-action.js +139 -0
  38. package/dist/lib/actions/forward-message-action.js.map +1 -0
  39. package/dist/lib/actions/get-attachment-action.d.ts +9 -0
  40. package/dist/lib/actions/get-attachment-action.d.ts.map +1 -0
  41. package/dist/lib/actions/get-attachment-action.js +80 -0
  42. package/dist/lib/actions/get-attachment-action.js.map +1 -0
  43. package/dist/lib/actions/get-draft-action.d.ts +7 -0
  44. package/dist/lib/actions/get-draft-action.d.ts.map +1 -0
  45. package/dist/lib/actions/get-draft-action.js +56 -0
  46. package/dist/lib/actions/get-draft-action.js.map +1 -0
  47. package/dist/lib/actions/get-label-action.d.ts +7 -0
  48. package/dist/lib/actions/get-label-action.d.ts.map +1 -0
  49. package/dist/lib/actions/get-label-action.js +55 -0
  50. package/dist/lib/actions/get-label-action.js.map +1 -0
  51. package/dist/lib/actions/get-mail-action.d.ts +7 -0
  52. package/dist/lib/actions/get-mail-action.d.ts.map +1 -0
  53. package/dist/lib/actions/get-mail-action.js +44 -0
  54. package/dist/lib/actions/get-mail-action.js.map +1 -0
  55. package/dist/lib/actions/get-profile-action.d.ts +5 -0
  56. package/dist/lib/actions/get-profile-action.d.ts.map +1 -0
  57. package/dist/lib/actions/get-profile-action.js +43 -0
  58. package/dist/lib/actions/get-profile-action.js.map +1 -0
  59. package/dist/lib/actions/get-thread-action.d.ts +9 -0
  60. package/dist/lib/actions/get-thread-action.d.ts.map +1 -0
  61. package/dist/lib/actions/get-thread-action.js +53 -0
  62. package/dist/lib/actions/get-thread-action.js.map +1 -0
  63. package/dist/lib/actions/list-drafts-action.d.ts +8 -0
  64. package/dist/lib/actions/list-drafts-action.d.ts.map +1 -0
  65. package/dist/lib/actions/list-drafts-action.js +62 -0
  66. package/dist/lib/actions/list-drafts-action.js.map +1 -0
  67. package/dist/lib/actions/list-history-action.d.ts +9 -0
  68. package/dist/lib/actions/list-history-action.d.ts.map +1 -0
  69. package/dist/lib/actions/list-history-action.js +74 -0
  70. package/dist/lib/actions/list-history-action.js.map +1 -0
  71. package/dist/lib/actions/list-labels-action.d.ts +5 -0
  72. package/dist/lib/actions/list-labels-action.d.ts.map +1 -0
  73. package/dist/lib/actions/list-labels-action.js +47 -0
  74. package/dist/lib/actions/list-labels-action.js.map +1 -0
  75. package/dist/lib/actions/list-threads-action.d.ts +10 -0
  76. package/dist/lib/actions/list-threads-action.d.ts.map +1 -0
  77. package/dist/lib/actions/list-threads-action.js +74 -0
  78. package/dist/lib/actions/list-threads-action.js.map +1 -0
  79. package/dist/lib/actions/reply-to-email-action.d.ts +16 -0
  80. package/dist/lib/actions/reply-to-email-action.d.ts.map +1 -0
  81. package/dist/lib/actions/reply-to-email-action.js +202 -0
  82. package/dist/lib/actions/reply-to-email-action.js.map +1 -0
  83. package/dist/lib/actions/request-approval-in-email.d.ts +15 -0
  84. package/dist/lib/actions/request-approval-in-email.d.ts.map +1 -0
  85. package/dist/lib/actions/request-approval-in-email.js +174 -0
  86. package/dist/lib/actions/request-approval-in-email.js.map +1 -0
  87. package/dist/lib/actions/search-email-action.d.ts +22 -0
  88. package/dist/lib/actions/search-email-action.d.ts.map +1 -0
  89. package/dist/lib/actions/search-email-action.js +179 -0
  90. package/dist/lib/actions/search-email-action.js.map +1 -0
  91. package/dist/lib/actions/send-draft-action.d.ts +7 -0
  92. package/dist/lib/actions/send-draft-action.d.ts.map +1 -0
  93. package/dist/lib/actions/send-draft-action.js +57 -0
  94. package/dist/lib/actions/send-draft-action.js.map +1 -0
  95. package/dist/lib/actions/send-email-action.d.ts +18 -0
  96. package/dist/lib/actions/send-email-action.d.ts.map +1 -0
  97. package/dist/lib/actions/send-email-action.js +196 -0
  98. package/dist/lib/actions/send-email-action.js.map +1 -0
  99. package/dist/lib/actions/stop-watch-action.d.ts +5 -0
  100. package/dist/lib/actions/stop-watch-action.d.ts.map +1 -0
  101. package/dist/lib/actions/stop-watch-action.js +41 -0
  102. package/dist/lib/actions/stop-watch-action.js.map +1 -0
  103. package/dist/lib/actions/update-draft-action.d.ts +17 -0
  104. package/dist/lib/actions/update-draft-action.d.ts.map +1 -0
  105. package/dist/lib/actions/update-draft-action.js +193 -0
  106. package/dist/lib/actions/update-draft-action.js.map +1 -0
  107. package/dist/lib/auth.d.ts +12 -0
  108. package/dist/lib/auth.d.ts.map +1 -0
  109. package/dist/lib/auth.js +108 -0
  110. package/dist/lib/auth.js.map +1 -0
  111. package/dist/lib/common/data.d.ts +132 -0
  112. package/dist/lib/common/data.d.ts.map +1 -0
  113. package/dist/lib/common/data.js +209 -0
  114. package/dist/lib/common/data.js.map +1 -0
  115. package/dist/lib/common/mime.d.ts +29 -0
  116. package/dist/lib/common/mime.d.ts.map +1 -0
  117. package/dist/lib/common/mime.js +59 -0
  118. package/dist/lib/common/mime.js.map +1 -0
  119. package/dist/lib/common/models.d.ts +86 -0
  120. package/dist/lib/common/models.d.ts.map +1 -0
  121. package/dist/lib/common/models.js +26 -0
  122. package/dist/lib/common/models.js.map +1 -0
  123. package/dist/lib/common/props.d.ts +28 -0
  124. package/dist/lib/common/props.d.ts.map +1 -0
  125. package/dist/lib/common/props.js +231 -0
  126. package/dist/lib/common/props.js.map +1 -0
  127. package/dist/lib/output-schemas.d.ts +29 -0
  128. package/dist/lib/output-schemas.d.ts.map +1 -0
  129. package/dist/lib/output-schemas.js +1029 -0
  130. package/dist/lib/output-schemas.js.map +1 -0
  131. package/dist/lib/triggers/new-attachment.d.ts +144 -0
  132. package/dist/lib/triggers/new-attachment.d.ts.map +1 -0
  133. package/dist/lib/triggers/new-attachment.js +150 -0
  134. package/dist/lib/triggers/new-attachment.js.map +1 -0
  135. package/dist/lib/triggers/new-conversation.d.ts +59 -0
  136. package/dist/lib/triggers/new-conversation.d.ts.map +1 -0
  137. package/dist/lib/triggers/new-conversation.js +308 -0
  138. package/dist/lib/triggers/new-conversation.js.map +1 -0
  139. package/dist/lib/triggers/new-email.d.ts +52 -0
  140. package/dist/lib/triggers/new-email.d.ts.map +1 -0
  141. package/dist/lib/triggers/new-email.js +135 -0
  142. package/dist/lib/triggers/new-email.js.map +1 -0
  143. package/dist/lib/triggers/new-label.d.ts +15 -0
  144. package/dist/lib/triggers/new-label.d.ts.map +1 -0
  145. package/dist/lib/triggers/new-label.js +82 -0
  146. package/dist/lib/triggers/new-label.js.map +1 -0
  147. package/dist/lib/triggers/new-labeled-email.d.ts +35 -0
  148. package/dist/lib/triggers/new-labeled-email.d.ts.map +1 -0
  149. package/dist/lib/triggers/new-labeled-email.js +147 -0
  150. package/dist/lib/triggers/new-labeled-email.js.map +1 -0
  151. package/package.json +53 -3
  152. package/src/index.ts +107 -0
  153. package/src/lib/actions/ai-get-message-action.ts +20 -0
  154. package/src/lib/actions/ai-reply-to-thread-action.ts +20 -0
  155. package/src/lib/actions/ai-search-email-action.ts +21 -0
  156. package/src/lib/actions/ai-send-email-action.ts +20 -0
  157. package/src/lib/actions/create-draft-action.ts +148 -0
  158. package/src/lib/actions/create-draft-reply-action.ts +314 -0
  159. package/src/lib/actions/delete-draft-action.ts +57 -0
  160. package/src/lib/actions/forward-message-action.ts +159 -0
  161. package/src/lib/actions/get-attachment-action.ts +83 -0
  162. package/src/lib/actions/get-draft-action.ts +53 -0
  163. package/src/lib/actions/get-label-action.ts +53 -0
  164. package/src/lib/actions/get-mail-action.ts +52 -0
  165. package/src/lib/actions/get-profile-action.ts +39 -0
  166. package/src/lib/actions/get-thread-action.ts +47 -0
  167. package/src/lib/actions/list-drafts-action.ts +72 -0
  168. package/src/lib/actions/list-history-action.ts +83 -0
  169. package/src/lib/actions/list-labels-action.ts +43 -0
  170. package/src/lib/actions/list-threads-action.ts +89 -0
  171. package/src/lib/actions/reply-to-email-action.ts +228 -0
  172. package/src/lib/actions/request-approval-in-email.ts +197 -0
  173. package/src/lib/actions/search-email-action.ts +219 -0
  174. package/src/lib/actions/send-draft-action.ts +54 -0
  175. package/src/lib/actions/send-email-action.ts +213 -0
  176. package/src/lib/actions/stop-watch-action.ts +37 -0
  177. package/src/lib/actions/update-draft-action.ts +211 -0
  178. package/src/lib/auth.ts +116 -0
  179. package/src/lib/common/data.ts +268 -0
  180. package/src/lib/common/mime.ts +82 -0
  181. package/src/lib/common/models.ts +91 -0
  182. package/src/lib/common/props.ts +262 -0
  183. package/src/lib/output-schemas.ts +1061 -0
  184. package/src/lib/triggers/new-attachment.ts +203 -0
  185. package/src/lib/triggers/new-conversation.ts +413 -0
  186. package/src/lib/triggers/new-email.ts +173 -0
  187. package/src/lib/triggers/new-label.ts +83 -0
  188. package/src/lib/triggers/new-labeled-email.ts +197 -0
@@ -0,0 +1,314 @@
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 { createDraftReplyActionOutputSchema } from '../output-schemas';
9
+
10
+ export const gmailCreateDraftReplyAction = createAction({
11
+ auth: gmailAuth,
12
+ name: 'create_draft_reply',
13
+ description: 'Creates a draft reply to an existing email.',
14
+ audience: 'human',
15
+ aiMetadata: {
16
+ description:
17
+ 'Builds a draft reply to an existing email and saves it (unsent) in the thread, addressing the original sender (reply) or all participants (reply all) and optionally quoting the original message. Use this when a human should review and send the response rather than sending it automatically; requires the Gmail message ID of the email being replied to. Not idempotent: each call creates a new draft.',
18
+ idempotent: false,
19
+ },
20
+ displayName: 'Create Draft Reply',
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: 'Draft Reply Body',
63
+ description: 'Your draft reply message content',
64
+ required: false,
65
+ }),
66
+ include_original_message: Property.Checkbox({
67
+ displayName: 'Include Original Message',
68
+ description: 'Include the original message content in the draft reply',
69
+ required: true,
70
+ defaultValue: true,
71
+ }),
72
+ sender_name: Property.ShortText({
73
+ displayName: 'Sender Name',
74
+ description: 'Optional sender name to display',
75
+ required: false,
76
+ }),
77
+ attachment: Property.File({
78
+ displayName: 'Attachment',
79
+ description: 'Optional file to attach to your draft reply',
80
+ required: false,
81
+ }),
82
+ attachment_name: Property.ShortText({
83
+ displayName: 'Attachment Name',
84
+ description: 'Custom name for the attachment',
85
+ required: false,
86
+ }),
87
+ },
88
+ outputSchema: createDraftReplyActionOutputSchema,
89
+ async run(context) {
90
+ const authClient = await createGoogleClient(context.auth);
91
+
92
+ const gmail = googleGmail({ version: 'v1', auth: authClient });
93
+
94
+ const originalMessage = await gmail.users.messages.get({
95
+ userId: 'me',
96
+ id: context.propsValue.message_id,
97
+ format: 'full',
98
+ });
99
+
100
+ if (!originalMessage.data || !originalMessage.data.payload) {
101
+ throw new Error('Could not fetch original message details');
102
+ }
103
+
104
+ const headers = originalMessage.data.payload.headers || [];
105
+ const headerMap = headers.reduce(
106
+ (acc: { [key: string]: string }, header) => {
107
+ if (header.name && header.value) {
108
+ acc[header.name.toLowerCase()] = header.value;
109
+ }
110
+ return acc;
111
+ },
112
+ {}
113
+ );
114
+
115
+ const originalSubject = headerMap['subject'] || '';
116
+ const originalFrom = headerMap['from'] || '';
117
+ const originalTo = headerMap['to'] || '';
118
+ const originalCc = headerMap['cc'] || '';
119
+ const originalReplyTo = headerMap['reply-to'] || '';
120
+ const originalMessageId = headerMap['message-id'] || '';
121
+ const originalReferences = headerMap['references'] || '';
122
+ const originalDate = headerMap['date'] || '';
123
+
124
+ let originalMessageContent = '';
125
+ if (context.propsValue.include_original_message) {
126
+ try {
127
+ const { parseStream } = await import('../common/data');
128
+
129
+ const originalMessageFull = await gmail.users.messages.get({
130
+ userId: 'me',
131
+ id: context.propsValue.message_id,
132
+ format: 'raw',
133
+ });
134
+
135
+ if (originalMessageFull.data.raw) {
136
+ const rawMessage = Buffer.from(
137
+ originalMessageFull.data.raw,
138
+ 'base64'
139
+ ).toString('utf-8');
140
+ const parsedMessage = await parseStream(rawMessage);
141
+
142
+ let messageText = parsedMessage.text || '';
143
+ if (!messageText && parsedMessage.html) {
144
+ messageText = parsedMessage.html
145
+ .replace(/<[^>]*>/g, '')
146
+ .replace(/&nbsp;/g, ' ')
147
+ .replace(/&amp;/g, '&')
148
+ .replace(/&lt;/g, '<')
149
+ .replace(/&gt;/g, '>');
150
+ }
151
+
152
+ if (messageText) {
153
+ const quotedLines = messageText
154
+ .split('\n')
155
+ .map((line) => `> ${line.trim()}`);
156
+ const senderInfo = `On ${originalDate}, ${originalFrom} wrote:`;
157
+ originalMessageContent = `${senderInfo}\n${quotedLines.join('\n')}`;
158
+ }
159
+ }
160
+ } catch (error) {
161
+ console.warn(
162
+ 'Could not extract original message content for quoting:',
163
+ error
164
+ );
165
+ originalMessageContent = `On ${originalDate}, ${originalFrom} wrote:\n> [Original message content could not be parsed]`;
166
+ }
167
+ }
168
+
169
+ const toRecipients: string[] = [];
170
+ const ccRecipients: string[] = [];
171
+
172
+ if (context.propsValue.reply_type === 'reply_all') {
173
+ const senderEmail = originalReplyTo || originalFrom;
174
+ if (senderEmail) {
175
+ toRecipients.push(senderEmail);
176
+ }
177
+
178
+ const currentUserEmail = await getUserEmail(context.auth, authClient);
179
+
180
+ if (originalTo) {
181
+ const toEmails = originalTo.split(',').map((email) => email.trim());
182
+ toRecipients.push(
183
+ ...toEmails.filter((email) => !email.includes(currentUserEmail || ''))
184
+ );
185
+ }
186
+
187
+ if (originalCc) {
188
+ const ccEmails = originalCc.split(',').map((email) => email.trim());
189
+ ccRecipients.push(
190
+ ...ccEmails.filter((email) => !email.includes(currentUserEmail || ''))
191
+ );
192
+ }
193
+ } else {
194
+ const senderEmail = originalReplyTo || originalFrom;
195
+ if (senderEmail) {
196
+ toRecipients.push(senderEmail);
197
+ }
198
+ }
199
+
200
+ let replySubject = originalSubject;
201
+ if (!replySubject.toLowerCase().startsWith('re:')) {
202
+ replySubject = `Re: ${replySubject}`;
203
+ }
204
+
205
+ let referencesHeader = originalMessageId;
206
+ if (originalReferences) {
207
+ referencesHeader = `${originalReferences} ${originalMessageId}`;
208
+ }
209
+
210
+ const senderEmail = await getUserEmail(context.auth, authClient);
211
+
212
+ let draftBody = context.propsValue.body || '';
213
+
214
+ if (context.propsValue.include_original_message && originalMessageContent) {
215
+ const separator =
216
+ context.propsValue.body_type === 'html'
217
+ ? '<br><br>--- Original Message ---<br>'
218
+ : '\n\n--- Original Message ---\n';
219
+
220
+ const quotedContent =
221
+ context.propsValue.body_type === 'html'
222
+ ? originalMessageContent.replace(/\n/g, '<br>')
223
+ : originalMessageContent;
224
+
225
+ draftBody = draftBody
226
+ ? `${draftBody}${separator}${quotedContent}`
227
+ : quotedContent;
228
+ }
229
+
230
+ const subjectBase64 = Buffer.from(replySubject).toString('base64');
231
+ const mailOptions: Mail.Options = {
232
+ to: toRecipients.join(', '),
233
+ cc: ccRecipients.length > 0 ? ccRecipients.join(', ') : undefined,
234
+ subject: `=?UTF-8?B?${subjectBase64}?=`,
235
+ text:
236
+ context.propsValue.body_type === 'plain_text' ? draftBody : undefined,
237
+ html: context.propsValue.body_type === 'html' ? draftBody : undefined,
238
+ attachments: [],
239
+ headers: [
240
+ {
241
+ key: 'In-Reply-To',
242
+ value: originalMessageId,
243
+ },
244
+ {
245
+ key: 'References',
246
+ value: referencesHeader,
247
+ },
248
+ ],
249
+ };
250
+
251
+ if (senderEmail) {
252
+ mailOptions.from = context.propsValue.sender_name
253
+ ? `${context.propsValue.sender_name} <${senderEmail}>`
254
+ : senderEmail;
255
+ }
256
+
257
+ if (context.propsValue.attachment) {
258
+ const lookupResult = mime.lookup(
259
+ context.propsValue.attachment.extension || ''
260
+ );
261
+ const attachmentOption: Attachment[] = [
262
+ {
263
+ filename:
264
+ context.propsValue.attachment_name ??
265
+ context.propsValue.attachment.filename,
266
+ content: context.propsValue.attachment.base64,
267
+ contentType: lookupResult || undefined,
268
+ encoding: 'base64',
269
+ },
270
+ ];
271
+ mailOptions.attachments = attachmentOption;
272
+ }
273
+
274
+ const mail: any = new MailComposer(mailOptions).compile();
275
+ mail.keepBcc = true;
276
+ const mailBody = await mail.build();
277
+
278
+ const encodedPayload = Buffer.from(mailBody)
279
+ .toString('base64')
280
+ .replace(/\+/g, '-')
281
+ .replace(/\//g, '_');
282
+
283
+ const draft = await gmail.users.drafts.create({
284
+ userId: 'me',
285
+ requestBody: {
286
+ message: {
287
+ threadId: originalMessage.data.threadId || undefined,
288
+ raw: encodedPayload,
289
+ },
290
+ },
291
+ });
292
+
293
+ return {
294
+ ...draft.data,
295
+ originalMessage: {
296
+ id: context.propsValue.message_id,
297
+ subject: originalSubject,
298
+ from: originalFrom,
299
+ to: originalTo,
300
+ date: originalDate,
301
+ threadId: originalMessage.data.threadId,
302
+ },
303
+ draftDetails: {
304
+ replyType: context.propsValue.reply_type,
305
+ recipients: {
306
+ to: toRecipients,
307
+ cc: ccRecipients,
308
+ },
309
+ subject: replySubject,
310
+ includeOriginal: context.propsValue.include_original_message,
311
+ },
312
+ };
313
+ },
314
+ });
@@ -0,0 +1,57 @@
1
+ import { createAction, Property } from '@agnocon/pieces-framework';
2
+ import { gmailAuth, createGoogleClient } from '../auth';
3
+ import { gmail as googleGmail } from '@googleapis/gmail';
4
+
5
+ export const gmailDeleteDraftAction = createAction({
6
+ auth: gmailAuth,
7
+ name: 'gmail_delete_draft',
8
+ displayName: 'Delete Draft',
9
+ description: 'Permanently delete a draft email by its ID.',
10
+ audience: 'ai',
11
+ aiMetadata: {
12
+ description:
13
+ 'Permanently removes a draft email by its draft ID. This only deletes the unsent draft (not a sent message) but the draft itself is not recoverable. Obtain the draft ID from List Drafts. Idempotent: if no draft matches the ID (already removed, or an invalid ID) it returns deleted:false instead of failing — check that flag rather than assuming a delete happened.',
14
+ idempotent: true,
15
+ },
16
+ props: {
17
+ draft_id: Property.ShortText({
18
+ displayName: 'Draft ID',
19
+ description: 'The ID of the draft to delete (obtain from List Drafts).',
20
+ required: true,
21
+ }),
22
+ },
23
+ async run(context) {
24
+ const authClient = await createGoogleClient(context.auth);
25
+ const gmail = googleGmail({ version: 'v1', auth: authClient });
26
+
27
+ try {
28
+ await gmail.users.drafts.delete({
29
+ userId: 'me',
30
+ id: context.propsValue.draft_id,
31
+ });
32
+ return {
33
+ success: true,
34
+ deleted: true,
35
+ draftId: context.propsValue.draft_id,
36
+ };
37
+ } catch (error: any) {
38
+ if (error.code === 403) {
39
+ throw new Error(
40
+ 'Insufficient permissions to delete a draft. Ensure the gmail.compose scope is granted.'
41
+ );
42
+ } else if (error.code === 404) {
43
+ return {
44
+ success: false,
45
+ deleted: false,
46
+ draftId: context.propsValue.draft_id,
47
+ message: `No draft with ID "${context.propsValue.draft_id}" was found — it may already be deleted, or the ID is invalid. Verify with List Drafts.`,
48
+ };
49
+ } else if (error.code === 429) {
50
+ throw new Error(
51
+ 'Gmail API rate limit exceeded. Please try again later.'
52
+ );
53
+ }
54
+ throw new Error(`Failed to delete draft: ${error.message}`);
55
+ }
56
+ },
57
+ });
@@ -0,0 +1,159 @@
1
+ import { ApFile, createAction, Property } from '@agnocon/pieces-framework';
2
+ import { gmailAuth, createGoogleClient, getUserEmail } from '../auth';
3
+ import { gmail as googleGmail } from '@googleapis/gmail';
4
+ import { GmailMime } from '../common/mime';
5
+ import { parseStream } from '../common/data';
6
+ import { gmailForwardMessageActionOutputSchema } from '../output-schemas';
7
+
8
+ export const gmailForwardMessageAction = createAction({
9
+ auth: gmailAuth,
10
+ name: 'gmail_forward_message',
11
+ displayName: 'Forward Message',
12
+ description: 'Forward an existing email to new recipients.',
13
+ audience: 'ai',
14
+ aiMetadata: {
15
+ description:
16
+ 'Forwards an existing email to one or more new recipients, quoting the original sender, subject, and body beneath an optional added note. Requires the Gmail message ID of the email to forward (obtain from Search Email or Get Message). Performs two calls: it fetches the original message, then sends the forwarded copy. Not idempotent: each call sends a new forwarded message.',
17
+ idempotent: false,
18
+ },
19
+ props: {
20
+ message_id: Property.ShortText({
21
+ displayName: 'Message ID',
22
+ description:
23
+ 'The Gmail message ID to forward (obtain from Search Email or Get Message).',
24
+ required: true,
25
+ }),
26
+ receiver: Property.Array({
27
+ displayName: 'Receiver Email (To)',
28
+ description: 'One or more recipients to forward the email to.',
29
+ required: true,
30
+ }),
31
+ cc: Property.Array({
32
+ displayName: 'CC Email',
33
+ required: false,
34
+ }),
35
+ bcc: Property.Array({
36
+ displayName: 'BCC Email',
37
+ required: false,
38
+ }),
39
+ note: Property.LongText({
40
+ displayName: 'Note',
41
+ description: 'Optional message to add above the forwarded content.',
42
+ required: false,
43
+ }),
44
+ sender_name: Property.ShortText({
45
+ displayName: 'Sender Name',
46
+ required: false,
47
+ }),
48
+ },
49
+ outputSchema: gmailForwardMessageActionOutputSchema,
50
+ async run(context) {
51
+ const authClient = await createGoogleClient(context.auth);
52
+ const gmail = googleGmail({ version: 'v1', auth: authClient });
53
+
54
+ try {
55
+ const original = await gmail.users.messages.get({
56
+ userId: 'me',
57
+ id: context.propsValue.message_id,
58
+ format: 'raw',
59
+ });
60
+
61
+ if (!original.data.raw) {
62
+ throw new Error('Could not fetch the original message to forward.');
63
+ }
64
+
65
+ const parsed = await parseStream(
66
+ Buffer.from(original.data.raw, 'base64').toString('utf-8')
67
+ );
68
+
69
+ const originalSubject = parsed.subject || '';
70
+ const forwardSubject = originalSubject.toLowerCase().startsWith('fwd:')
71
+ ? originalSubject
72
+ : `Fwd: ${originalSubject}`;
73
+
74
+ const originalFrom =
75
+ typeof parsed.from?.text === 'string' ? parsed.from.text : '';
76
+ const originalTo =
77
+ parsed.to && !Array.isArray(parsed.to) ? parsed.to.text : '';
78
+ const originalDate = parsed.date ? parsed.date.toUTCString() : '';
79
+
80
+ const note = context.propsValue.note
81
+ ? `${context.propsValue.note}\n\n`
82
+ : '';
83
+ const htmlAsText = parsed.html ? parsed.html.replace(/<[^>]*>/g, '') : '';
84
+ const originalBody = parsed.text || htmlAsText || '';
85
+ const quotedBody = originalBody
86
+ .split('\n')
87
+ .map((line) => `> ${line}`)
88
+ .join('\n');
89
+ const forwardedBody =
90
+ `${note}---------- Forwarded message ----------\n` +
91
+ `From: ${originalFrom}\n` +
92
+ `Date: ${originalDate}\n` +
93
+ `Subject: ${originalSubject}\n` +
94
+ `To: ${originalTo}\n\n` +
95
+ `${quotedBody}`;
96
+
97
+ const receiver = (context.propsValue.receiver as string[]).filter(
98
+ (email) => email !== ''
99
+ );
100
+ const cc = (context.propsValue.cc as string[] | undefined)?.filter(
101
+ (email) => email !== ''
102
+ );
103
+ const bcc = (context.propsValue.bcc as string[] | undefined)?.filter(
104
+ (email) => email !== ''
105
+ );
106
+
107
+ const senderEmail = await getUserEmail(context.auth, authClient);
108
+ const from = senderEmail
109
+ ? context.propsValue.sender_name
110
+ ? `${context.propsValue.sender_name} <${senderEmail}>`
111
+ : senderEmail
112
+ : undefined;
113
+
114
+ const forwardedAttachments = (parsed.attachments || [])
115
+ .filter((attachment) => attachment.content)
116
+ .map((attachment) => ({
117
+ file: new ApFile(
118
+ attachment.filename ?? `attachment-${Date.now()}`,
119
+ Buffer.from(attachment.content),
120
+ attachment.filename?.split('.').pop()
121
+ ),
122
+ name: attachment.filename ?? undefined,
123
+ }));
124
+
125
+ const raw = await GmailMime.buildRawMessage({
126
+ to: receiver,
127
+ cc,
128
+ bcc,
129
+ from,
130
+ subject: forwardSubject,
131
+ bodyType: 'plain_text',
132
+ body: forwardedBody,
133
+ attachments: forwardedAttachments,
134
+ });
135
+
136
+ const response = await gmail.users.messages.send({
137
+ userId: 'me',
138
+ requestBody: { raw },
139
+ });
140
+
141
+ return response.data;
142
+ } catch (error: any) {
143
+ if (error.code === 403) {
144
+ throw new Error(
145
+ 'Insufficient permissions to forward the message. Ensure the gmail.send and gmail.readonly scopes are granted.'
146
+ );
147
+ } else if (error.code === 404) {
148
+ throw new Error(
149
+ `Original message not found: "${context.propsValue.message_id}". Use Search Email to find a valid message ID.`
150
+ );
151
+ } else if (error.code === 429) {
152
+ throw new Error(
153
+ 'Gmail API rate limit exceeded. Please try again later.'
154
+ );
155
+ }
156
+ throw new Error(`Failed to forward message: ${error.message}`);
157
+ }
158
+ },
159
+ });
@@ -0,0 +1,83 @@
1
+ import { createAction, Property } from '@agnocon/pieces-framework';
2
+ import { gmailAuth, createGoogleClient } from '../auth';
3
+ import { gmail as googleGmail } from '@googleapis/gmail';
4
+ import { gmailGetAttachmentActionOutputSchema } from '../output-schemas';
5
+
6
+ export const gmailGetAttachmentAction = createAction({
7
+ auth: gmailAuth,
8
+ name: 'gmail_get_attachment',
9
+ displayName: 'Get Attachment',
10
+ description: 'Download an email attachment by message ID and attachment ID.',
11
+ audience: 'ai',
12
+ aiMetadata: {
13
+ description:
14
+ "Fetches one email attachment's raw bytes by message ID + attachment ID, returning a stored file plus size. Note: Get Message already returns each attachment's downloaded content directly, so prefer that; use this only when you separately hold a raw Gmail attachment ID (from the message payload parts) and need the bytes on their own. Get the message ID from Search Email. Idempotent: a read-only fetch that does not modify the mailbox.",
15
+ idempotent: true,
16
+ },
17
+ props: {
18
+ message_id: Property.ShortText({
19
+ displayName: 'Message ID',
20
+ description:
21
+ 'The Gmail message ID the attachment belongs to (obtain from Search Email or Get Message).',
22
+ required: true,
23
+ }),
24
+ attachment_id: Property.ShortText({
25
+ displayName: 'Attachment ID',
26
+ description:
27
+ 'The raw Gmail attachment ID, taken from the message payload parts (parts[].body.attachmentId of a full-format message). Note: Get Message returns attachment content directly, so this field is only needed for the raw-ID fetch path.',
28
+ required: true,
29
+ }),
30
+ file_name: Property.ShortText({
31
+ displayName: 'File Name',
32
+ description: 'Optional name to give the downloaded file.',
33
+ required: false,
34
+ }),
35
+ },
36
+ outputSchema: gmailGetAttachmentActionOutputSchema,
37
+ async run(context) {
38
+ const authClient = await createGoogleClient(context.auth);
39
+ const gmail = googleGmail({ version: 'v1', auth: authClient });
40
+
41
+ try {
42
+ const response = await gmail.users.messages.attachments.get({
43
+ userId: 'me',
44
+ messageId: context.propsValue.message_id,
45
+ id: context.propsValue.attachment_id,
46
+ });
47
+
48
+ const data = response.data.data;
49
+ if (!data) {
50
+ throw new Error('Attachment returned no data.');
51
+ }
52
+
53
+ const fileName =
54
+ context.propsValue.file_name ||
55
+ `attachment-${context.propsValue.attachment_id}`;
56
+ const fileUrl = await context.files.write({
57
+ fileName,
58
+ data: Buffer.from(data, 'base64'),
59
+ });
60
+
61
+ return {
62
+ fileName,
63
+ size: response.data.size,
64
+ data: fileUrl,
65
+ };
66
+ } catch (error: any) {
67
+ if (error.code === 403) {
68
+ throw new Error(
69
+ 'Insufficient permissions to read the attachment. Ensure the gmail.readonly scope is granted.'
70
+ );
71
+ } else if (error.code === 404) {
72
+ throw new Error(
73
+ 'Attachment or message not found. Verify the message ID and attachment ID via Get Message.'
74
+ );
75
+ } else if (error.code === 429) {
76
+ throw new Error(
77
+ 'Gmail API rate limit exceeded. Please try again later.'
78
+ );
79
+ }
80
+ throw new Error(`Failed to get attachment: ${error.message}`);
81
+ }
82
+ },
83
+ });
@@ -0,0 +1,53 @@
1
+ import { createAction, Property } from '@agnocon/pieces-framework';
2
+ import { gmailAuth, createGoogleClient } from '../auth';
3
+ import { gmail as googleGmail } from '@googleapis/gmail';
4
+ import { gmailGetDraftActionOutputSchema } from '../output-schemas';
5
+
6
+ export const gmailGetDraftAction = createAction({
7
+ auth: gmailAuth,
8
+ name: 'gmail_get_draft',
9
+ displayName: 'Get Draft',
10
+ description: 'Get a single draft email by its ID.',
11
+ audience: 'ai',
12
+ aiMetadata: {
13
+ description:
14
+ 'Fetches a single draft email by its draft ID, returning the draft and its underlying message. Use this to inspect a draft before sending or updating it; obtain the draft ID from List Drafts. Idempotent: a read-only lookup that does not modify the mailbox.',
15
+ idempotent: true,
16
+ },
17
+ props: {
18
+ draft_id: Property.ShortText({
19
+ displayName: 'Draft ID',
20
+ description: 'The ID of the draft to read (obtain from List Drafts).',
21
+ required: true,
22
+ }),
23
+ },
24
+ outputSchema: gmailGetDraftActionOutputSchema,
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.get({
31
+ userId: 'me',
32
+ id: context.propsValue.draft_id,
33
+ format: 'full',
34
+ });
35
+ return response.data;
36
+ } catch (error: any) {
37
+ if (error.code === 403) {
38
+ throw new Error(
39
+ 'Insufficient permissions to read drafts. Ensure the gmail.readonly scope is granted.'
40
+ );
41
+ } else if (error.code === 404) {
42
+ throw new Error(
43
+ `Draft not found: "${context.propsValue.draft_id}". Use List Drafts to find a valid draft ID.`
44
+ );
45
+ } else if (error.code === 429) {
46
+ throw new Error(
47
+ 'Gmail API rate limit exceeded. Please try again later.'
48
+ );
49
+ }
50
+ throw new Error(`Failed to get draft: ${error.message}`);
51
+ }
52
+ },
53
+ });