@littlebearapps/outlook-assistant 3.13.0 → 3.14.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.
Files changed (67) hide show
  1. package/.env.example +30 -3
  2. package/README.md +67 -27
  3. package/advanced/index.js +44 -174
  4. package/auth/auth-errors.js +23 -1
  5. package/auth/oauth-server.js +7 -1
  6. package/auth/token-manager.js +7 -3
  7. package/auth/token-storage.js +28 -30
  8. package/auth/tools.js +61 -82
  9. package/calendar/attendees.js +36 -0
  10. package/calendar/cancel.js +9 -25
  11. package/calendar/create.js +42 -48
  12. package/calendar/decline.js +10 -25
  13. package/calendar/delete.js +10 -25
  14. package/calendar/index.js +20 -37
  15. package/calendar/list.js +4 -16
  16. package/calendar/preview.js +461 -0
  17. package/calendar/update.js +55 -83
  18. package/categories/index.js +68 -265
  19. package/config.js +29 -1
  20. package/contacts/index.js +72 -128
  21. package/email/attachments.js +43 -125
  22. package/email/conversations.js +44 -78
  23. package/email/delta.js +69 -46
  24. package/email/draft.js +170 -103
  25. package/email/export.js +145 -110
  26. package/email/folder-utils.js +3 -2
  27. package/email/headers.js +11 -49
  28. package/email/index.js +86 -110
  29. package/email/list.js +4 -17
  30. package/email/mail-tips.js +86 -57
  31. package/email/mark-as-read.js +13 -49
  32. package/email/mime.js +39 -51
  33. package/email/read.js +16 -50
  34. package/email/search.js +47 -87
  35. package/email/send.js +82 -48
  36. package/folder/create.js +6 -25
  37. package/folder/delete.js +117 -38
  38. package/folder/index.js +19 -17
  39. package/folder/list.js +5 -17
  40. package/folder/move.js +13 -42
  41. package/folder/resolve.js +11 -6
  42. package/folder/stats.js +18 -27
  43. package/index.js +39 -45
  44. package/llms-install.md +22 -4
  45. package/llms.txt +20 -11
  46. package/outlook-auth-server.js +10 -3
  47. package/package.json +4 -1
  48. package/request-handler.js +217 -116
  49. package/rules/create.js +28 -71
  50. package/rules/index.js +52 -93
  51. package/rules/list.js +7 -19
  52. package/rules/rule-builder.js +59 -22
  53. package/rules/update.js +27 -61
  54. package/server.js +41 -0
  55. package/settings/index.js +162 -145
  56. package/tools.js +30 -0
  57. package/utils/field-presets.js +4 -2
  58. package/utils/graph-api.js +65 -22
  59. package/utils/logger.js +251 -0
  60. package/utils/mock-data.js +91 -2
  61. package/utils/read-only.js +59 -0
  62. package/utils/response-formatter.js +54 -15
  63. package/utils/risk-classes.js +324 -0
  64. package/utils/safe-write.js +372 -6
  65. package/utils/safety.js +247 -42
  66. package/utils/server-instructions.js +73 -0
  67. package/utils/tool-error.js +33 -0
@@ -6,6 +6,7 @@
6
6
  */
7
7
  const { callGraphAPI } = require('../utils/graph-api');
8
8
  const { ensureAuthenticated } = require('../auth');
9
+ const { toolError, authRequiredError } = require('../utils/tool-error');
9
10
 
10
11
  /**
11
12
  * All available mail tip types from Microsoft Graph API
@@ -25,17 +26,27 @@ const MAIL_TIP_TYPES = [
25
26
 
26
27
  /**
27
28
  * Format mail tips into readable markdown
29
+ *
30
+ * Graph's mailTips resource reports `mailboxFull`, `deliveryRestricted` and
31
+ * `isModerated` as booleans. The older `mailboxFullStatus`,
32
+ * `deliveryRestriction` and `moderationStatus` shapes are still recognised.
28
33
  * @param {Array} mailTips - Array of mail tip objects from Graph API
29
- * @returns {string} - Formatted markdown output
34
+ * @returns {{formatted: string, warningCount: number, issues: Array<{address: string, type: string}>}}
35
+ * `issues` lists each flagged condition per recipient, with `type` one of
36
+ * outOfOffice, mailboxFull, customTip, deliveryRestricted, moderated,
37
+ * external or externalMembers. Every issue is also a warning in the text,
38
+ * so the text never shows ✓ for a recipient send-email would refuse
30
39
  */
31
40
  function formatMailTips(mailTips) {
32
41
  const lines = [];
42
+ const issues = [];
33
43
  let warningCount = 0;
34
44
 
35
45
  for (const tip of mailTips) {
36
46
  const email = tip.emailAddress?.address || 'Unknown';
37
47
  const tipLines = [];
38
48
  const warnings = [];
49
+ const flag = (type) => issues.push({ address: email, type });
39
50
 
40
51
  // Out-of-office / automatic replies
41
52
  if (
@@ -43,6 +54,7 @@ function formatMailTips(mailTips) {
43
54
  tip.automaticReplies.message.trim() !== ''
44
55
  ) {
45
56
  warnings.push('Out of Office');
57
+ flag('outOfOffice');
46
58
  const reply = tip.automaticReplies;
47
59
  tipLines.push(
48
60
  ` **Out of Office**: ${reply.message.replace(/\n/g, ' ').substring(0, 200)}`
@@ -57,8 +69,9 @@ function formatMailTips(mailTips) {
57
69
  }
58
70
 
59
71
  // Mailbox full
60
- if (tip.mailboxFullStatus) {
72
+ if (tip.mailboxFull === true || tip.mailboxFullStatus === true) {
61
73
  warnings.push('Mailbox Full');
74
+ flag('mailboxFull');
62
75
  tipLines.push(
63
76
  ` **Mailbox Full**: Recipient's mailbox is full — delivery may fail`
64
77
  );
@@ -67,30 +80,38 @@ function formatMailTips(mailTips) {
67
80
  // Custom mail tip (admin-configured)
68
81
  if (tip.customMailTip) {
69
82
  warnings.push('Custom Tip');
83
+ flag('customTip');
70
84
  tipLines.push(` **Notice**: ${tip.customMailTip}`);
71
85
  }
72
86
 
73
87
  // Delivery restriction
74
- if (tip.deliveryRestriction) {
75
- const restriction = tip.deliveryRestriction;
76
- if (restriction.isDeliveryRestricted) {
77
- warnings.push('Delivery Restricted');
78
- tipLines.push(
79
- ` **Delivery Restricted**: ${restriction.message || 'Cannot deliver to this recipient'}`
80
- );
81
- }
88
+ if (
89
+ tip.deliveryRestricted === true ||
90
+ tip.deliveryRestriction?.isDeliveryRestricted
91
+ ) {
92
+ warnings.push('Delivery Restricted');
93
+ flag('deliveryRestricted');
94
+ tipLines.push(
95
+ ` **Delivery Restricted**: ${tip.deliveryRestriction?.message || 'Cannot deliver to this recipient'}`
96
+ );
82
97
  }
83
98
 
84
99
  // Moderation status
85
- if (tip.moderationStatus && tip.moderationStatus !== 'notModerated') {
100
+ if (
101
+ tip.isModerated === true ||
102
+ (tip.moderationStatus && tip.moderationStatus !== 'notModerated')
103
+ ) {
86
104
  warnings.push('Moderated');
105
+ flag('moderated');
87
106
  tipLines.push(
88
107
  ` **Moderated**: Messages to this recipient require approval`
89
108
  );
90
109
  }
91
110
 
92
- // Recipient scope (external)
93
- if (tip.recipientScope === 'external') {
111
+ // Recipient scope: external, externalPartner or externalNonPartner
112
+ if (/^external/i.test(tip.recipientScope || '')) {
113
+ warnings.push('External');
114
+ flag('external');
94
115
  tipLines.push(` **External**: Recipient is outside your organisation`);
95
116
  }
96
117
 
@@ -101,12 +122,27 @@ function formatMailTips(mailTips) {
101
122
  }
102
123
 
103
124
  // Group member counts
104
- if (tip.totalMemberCount > 0) {
125
+ if (tip.externalMemberCount > 0) {
126
+ warnings.push('External Members');
127
+ flag('externalMembers');
128
+ }
129
+ if (tip.totalMemberCount > 0 || tip.externalMemberCount > 0) {
105
130
  tipLines.push(
106
131
  ` *Group members*: ${tip.totalMemberCount} total (${tip.externalMemberCount || 0} external)`
107
132
  );
108
133
  }
109
134
 
135
+ // Graph couldn't check this recipient, so its other fields say nothing.
136
+ if (tip.error) {
137
+ warnings.push('Not Checked');
138
+ const code = /^[A-Za-z0-9]{1,64}$/.test(tip.error.code || '')
139
+ ? ` (${tip.error.code})`
140
+ : '';
141
+ tipLines.push(
142
+ ` **Not checked**: Graph returned an error for this recipient${code}`
143
+ );
144
+ }
145
+
110
146
  // Build section for this recipient
111
147
  const statusIcon = warnings.length > 0 ? '⚠' : '✓';
112
148
  lines.push(`### ${statusIcon} ${email}`);
@@ -124,7 +160,29 @@ function formatMailTips(mailTips) {
124
160
  lines.push('');
125
161
  }
126
162
 
127
- return { formatted: lines.join('\n'), warningCount };
163
+ return { formatted: lines.join('\n'), warningCount, issues };
164
+ }
165
+
166
+ /**
167
+ * Whether Graph returned anything actionable for a recipient: any field a
168
+ * personal Outlook.com account leaves out. A recipientScope of `none` (or
169
+ * none at all) says nothing.
170
+ * @param {object} tip - One mailTips object from Graph
171
+ * @returns {boolean}
172
+ */
173
+ function hasTipContent(tip) {
174
+ return Boolean(
175
+ tip.error ||
176
+ tip.mailboxFull ||
177
+ tip.deliveryRestricted ||
178
+ tip.isModerated ||
179
+ tip.automaticReplies?.message ||
180
+ tip.maxMessageSize ||
181
+ tip.totalMemberCount ||
182
+ tip.externalMemberCount ||
183
+ tip.customMailTip ||
184
+ (tip.recipientScope && tip.recipientScope !== 'none')
185
+ );
128
186
  }
129
187
 
130
188
  /**
@@ -136,14 +194,7 @@ async function handleGetMailTips(args) {
136
194
  const { recipients, tipTypes } = args;
137
195
 
138
196
  if (!recipients || recipients.length === 0) {
139
- return {
140
- content: [
141
- {
142
- type: 'text',
143
- text: 'At least one recipient email address is required.',
144
- },
145
- ],
146
- };
197
+ return toolError('At least one recipient email address is required.');
147
198
  }
148
199
 
149
200
  // Normalise recipients to a clean array of email strings
@@ -171,14 +222,7 @@ async function handleGetMailTips(args) {
171
222
  recipientList = recipientList.filter((e) => e.length > 0);
172
223
 
173
224
  if (recipientList.length === 0) {
174
- return {
175
- content: [
176
- {
177
- type: 'text',
178
- text: 'At least one valid recipient email address is required.',
179
- },
180
- ],
181
- };
225
+ return toolError('At least one valid recipient email address is required.');
182
226
  }
183
227
 
184
228
  try {
@@ -206,28 +250,23 @@ async function handleGetMailTips(args) {
206
250
  text: 'No mail tips returned. Mail Tips is M365-only — personal Outlook.com accounts return empty responses, so recipient validation is unavailable on this account.',
207
251
  },
208
252
  ],
253
+ _meta: {
254
+ recipientCount: 0,
255
+ warningCount: 0,
256
+ allEmpty: true,
257
+ issues: [],
258
+ },
209
259
  };
210
260
  }
211
261
 
212
- const { formatted, warningCount } = formatMailTips(mailTips);
262
+ const { formatted, warningCount, issues } = formatMailTips(mailTips);
213
263
 
214
264
  // F-23: Detect a "fully empty" tips response — every recipient
215
265
  // returned with no actionable fields. Personal Outlook.com
216
266
  // accounts surface this as a successful empty response rather
217
267
  // than a feature-unsupported error, leading to false confidence
218
268
  // when callers see "No issues detected".
219
- const allEmpty = mailTips.every((tip) => {
220
- const hasContent =
221
- tip.recipientNotFound ||
222
- tip.mailboxFull ||
223
- tip.deliveryRestricted ||
224
- tip.isModerated ||
225
- tip.automaticReplies?.message ||
226
- tip.maxMessageSize ||
227
- tip.totalMemberCount ||
228
- tip.customMailTip;
229
- return !hasContent;
230
- });
269
+ const allEmpty = mailTips.every((tip) => !hasTipContent(tip));
231
270
 
232
271
  let header = `# Mail Tips\n\n`;
233
272
  header += `**Recipients checked**: ${mailTips.length}\n`;
@@ -244,24 +283,14 @@ async function handleGetMailTips(args) {
244
283
  recipientCount: mailTips.length,
245
284
  warningCount,
246
285
  allEmpty,
286
+ issues,
247
287
  },
248
288
  };
249
289
  } catch (error) {
250
290
  if (error.message === 'Authentication required') {
251
- return {
252
- content: [
253
- {
254
- type: 'text',
255
- text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
256
- },
257
- ],
258
- };
291
+ return authRequiredError();
259
292
  }
260
- return {
261
- content: [
262
- { type: 'text', text: `Error getting mail tips: ${error.message}` },
263
- ],
264
- };
293
+ return toolError(`Error getting mail tips: ${error.message}`);
265
294
  }
266
295
  }
267
296
 
@@ -5,6 +5,8 @@ const _config = require('../config'); // Reserved for future use
5
5
  const { callGraphAPI } = require('../utils/graph-api');
6
6
  const { ensureAuthenticated } = require('../auth');
7
7
  const { buildMailboxPrefix } = require('../utils/mailbox');
8
+ const { toolError, authRequiredError } = require('../utils/tool-error');
9
+ const { log } = require('../utils/logger');
8
10
 
9
11
  /**
10
12
  * Mark email as read handler
@@ -17,14 +19,7 @@ async function handleMarkAsRead(args) {
17
19
  const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
18
20
 
19
21
  if (!emailId) {
20
- return {
21
- content: [
22
- {
23
- type: 'text',
24
- text: 'Email ID is required.',
25
- },
26
- ],
27
- };
22
+ return toolError('Email ID is required.');
28
23
  }
29
24
 
30
25
  try {
@@ -56,60 +51,29 @@ async function handleMarkAsRead(args) {
56
51
  ],
57
52
  };
58
53
  } catch (error) {
59
- console.error(
54
+ log.debug(
60
55
  `Error marking email as ${isRead ? 'read' : 'unread'}: ${error.message}`
61
56
  );
62
57
 
63
58
  // Improved error handling with more specific messages
64
59
  if (error.message.includes("doesn't belong to the targeted mailbox")) {
65
- return {
66
- content: [
67
- {
68
- type: 'text',
69
- text: `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`,
70
- },
71
- ],
72
- };
60
+ return toolError(
61
+ `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`
62
+ );
73
63
  } else if (error.message.includes('UNAUTHORIZED')) {
74
- return {
75
- content: [
76
- {
77
- type: 'text',
78
- text: 'Authentication failed. Please re-authenticate and try again.',
79
- },
80
- ],
81
- };
64
+ return authRequiredError();
82
65
  } else {
83
- return {
84
- content: [
85
- {
86
- type: 'text',
87
- text: `Failed to mark email as ${isRead ? 'read' : 'unread'}: ${error.message}`,
88
- },
89
- ],
90
- };
66
+ return toolError(
67
+ `Failed to mark email as ${isRead ? 'read' : 'unread'}: ${error.message}`
68
+ );
91
69
  }
92
70
  }
93
71
  } catch (error) {
94
72
  if (error.message === 'Authentication required') {
95
- return {
96
- content: [
97
- {
98
- type: 'text',
99
- text: "Authentication required. Please use the 'authenticate' tool first.",
100
- },
101
- ],
102
- };
73
+ return authRequiredError();
103
74
  }
104
75
 
105
- return {
106
- content: [
107
- {
108
- type: 'text',
109
- text: `Error accessing email: ${error.message}`,
110
- },
111
- ],
112
- };
76
+ return toolError(`Error accessing email: ${error.message}`);
113
77
  }
114
78
  }
115
79
 
package/email/mime.js CHANGED
@@ -7,6 +7,24 @@
7
7
  const { callGraphAPIRaw } = require('../utils/graph-api');
8
8
  const { ensureAuthenticated } = require('../auth');
9
9
  const { buildMailboxPrefix } = require('../utils/mailbox');
10
+ const { toolError, authRequiredError } = require('../utils/tool-error');
11
+ const { log } = require('../utils/logger');
12
+
13
+ /**
14
+ * The longest prefix of `text` that fits in `maxBytes` of UTF-8 without
15
+ * splitting a character (which would render as U+FFFD).
16
+ * @param {string} text
17
+ * @param {number} maxBytes
18
+ * @returns {string}
19
+ */
20
+ function utf8Prefix(text, maxBytes) {
21
+ const bytes = Buffer.from(text, 'utf8');
22
+ if (bytes.length <= maxBytes) return text;
23
+ let end = maxBytes;
24
+ // Step back over continuation bytes (10xxxxxx) to a character start.
25
+ while (end > 0 && (bytes[end] & 0xc0) === 0x80) end--;
26
+ return bytes.subarray(0, end).toString('utf8');
27
+ }
10
28
 
11
29
  /**
12
30
  * Parse MIME headers from raw content
@@ -98,14 +116,7 @@ async function handleGetMimeContent(args) {
98
116
  const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
99
117
 
100
118
  if (!emailId) {
101
- return {
102
- content: [
103
- {
104
- type: 'text',
105
- text: 'Email ID is required.',
106
- },
107
- ],
108
- };
119
+ return toolError('Email ID is required.');
109
120
  }
110
121
 
111
122
  try {
@@ -117,14 +128,9 @@ async function handleGetMimeContent(args) {
117
128
  const mimeContent = await callGraphAPIRaw(accessToken, emailId, prefix);
118
129
 
119
130
  if (!mimeContent) {
120
- return {
121
- content: [
122
- {
123
- type: 'text',
124
- text: `Failed to retrieve MIME content for email ${emailId}.`,
125
- },
126
- ],
127
- };
131
+ return toolError(
132
+ `Failed to retrieve MIME content for email ${emailId}.`
133
+ );
128
134
  }
129
135
 
130
136
  const stats = getMimeStats(mimeContent);
@@ -133,7 +139,13 @@ async function handleGetMimeContent(args) {
133
139
  // Check size limit
134
140
  if (maxSize > 0 && stats.bytes > maxSize) {
135
141
  if (headersOnly) {
136
- // Return just headers if over limit
142
+ // Return just headers if over limit, themselves capped at
143
+ // maxSize (#306): a large header block used to come back whole.
144
+ const headerBytes = Buffer.byteLength(parsed.headerSection, 'utf8');
145
+ const headerText =
146
+ headerBytes > maxSize
147
+ ? `${utf8Prefix(parsed.headerSection, maxSize)}\n… (headers cut at maxSize ${maxSize} bytes of ${headerBytes})`
148
+ : parsed.headerSection;
137
149
  return {
138
150
  content: [
139
151
  {
@@ -142,7 +154,7 @@ async function handleGetMimeContent(args) {
142
154
  `# MIME Content (Headers Only - Content Truncated)\n\n` +
143
155
  `**Size**: ${stats.formattedSize} (exceeds ${maxSize} byte limit)\n` +
144
156
  `**Lines**: ${stats.lines}\n\n` +
145
- `## MIME Headers\n\n\`\`\`\n${parsed.headerSection}\n\`\`\``,
157
+ `## MIME Headers\n\n\`\`\`\n${headerText}\n\`\`\``,
146
158
  },
147
159
  ],
148
160
  _meta: {
@@ -174,6 +186,7 @@ async function handleGetMimeContent(args) {
174
186
  truncated: true,
175
187
  maxSizeExceeded: true,
176
188
  },
189
+ isError: true,
177
190
  };
178
191
  }
179
192
 
@@ -238,53 +251,28 @@ async function handleGetMimeContent(args) {
238
251
  },
239
252
  };
240
253
  } catch (error) {
241
- console.error(`Error getting MIME content: ${error.message}`);
254
+ log.debug(`Error getting MIME content: ${error.message}`);
242
255
 
243
256
  if (error.message.includes("doesn't belong to the targeted mailbox")) {
244
- return {
245
- content: [
246
- {
247
- type: 'text',
248
- text: `The email ID seems invalid or doesn't belong to your mailbox.`,
249
- },
250
- ],
251
- };
257
+ return toolError(
258
+ `The email ID seems invalid or doesn't belong to your mailbox.`
259
+ );
252
260
  }
253
261
 
254
- return {
255
- content: [
256
- {
257
- type: 'text',
258
- text: `Failed to get MIME content: ${error.message}`,
259
- },
260
- ],
261
- };
262
+ return toolError(`Failed to get MIME content: ${error.message}`);
262
263
  }
263
264
  } catch (error) {
264
265
  if (error.message === 'Authentication required') {
265
- return {
266
- content: [
267
- {
268
- type: 'text',
269
- text: "Authentication required. Please use the 'authenticate' tool first.",
270
- },
271
- ],
272
- };
266
+ return authRequiredError();
273
267
  }
274
268
 
275
- return {
276
- content: [
277
- {
278
- type: 'text',
279
- text: `Error accessing email: ${error.message}`,
280
- },
281
- ],
282
- };
269
+ return toolError(`Error accessing email: ${error.message}`);
283
270
  }
284
271
  }
285
272
 
286
273
  module.exports = {
287
274
  handleGetMimeContent,
275
+ utf8Prefix,
288
276
  parseMimeHeaders,
289
277
  getMimeStats,
290
278
  };
package/email/read.js CHANGED
@@ -9,9 +9,12 @@ const { ensureAuthenticated } = require('../auth');
9
9
  const {
10
10
  formatEmailContent,
11
11
  VERBOSITY,
12
+ DEFAULT_LIMITS,
12
13
  } = require('../utils/response-formatter');
13
14
  const { getEmailFields } = require('../utils/field-presets');
14
15
  const { buildMailboxPrefix } = require('../utils/mailbox');
16
+ const { toolError, authRequiredError } = require('../utils/tool-error');
17
+ const { log } = require('../utils/logger');
15
18
 
16
19
  /**
17
20
  * Get field preset based on verbosity and options
@@ -48,17 +51,11 @@ async function handleReadEmail(args) {
48
51
  const includeHeaders = args.includeHeaders || false;
49
52
  // Message IDs are mailbox-scoped: an ID issued by a shared/delegated mailbox
50
53
  // is not resolvable under /me. Route to /users/{mailbox} when supplied.
51
- const prefix = buildMailboxPrefix(args.sharedMailbox || args.email || null);
54
+ const sharedMailbox = args.sharedMailbox || args.email || null;
55
+ const prefix = buildMailboxPrefix(sharedMailbox);
52
56
 
53
57
  if (!emailId) {
54
- return {
55
- content: [
56
- {
57
- type: 'text',
58
- text: 'Email ID is required.',
59
- },
60
- ],
61
- };
58
+ return toolError('Email ID is required.');
62
59
  }
63
60
 
64
61
  try {
@@ -85,20 +82,15 @@ async function handleReadEmail(args) {
85
82
  );
86
83
 
87
84
  if (!email) {
88
- return {
89
- content: [
90
- {
91
- type: 'text',
92
- text: `Email with ID ${emailId} not found.`,
93
- },
94
- ],
95
- };
85
+ return toolError(`Email with ID ${emailId} not found.`);
96
86
  }
97
87
 
98
88
  // Format using shared formatter (returns Markdown)
99
89
  const formattedOutput = formatEmailContent(email, verbosity, {
100
90
  includeHeaders: includeHeaders,
101
91
  includeAllHeaders: false, // Only important headers by default
92
+ sharedMailbox,
93
+ maxFullBodyChars: DEFAULT_LIMITS.maxFullBodyChars,
102
94
  });
103
95
 
104
96
  return {
@@ -116,49 +108,23 @@ async function handleReadEmail(args) {
116
108
  },
117
109
  };
118
110
  } catch (error) {
119
- console.error(`Error reading email: ${error.message}`);
111
+ log.debug(`Error reading email: ${error.message}`);
120
112
 
121
113
  // Improved error handling with more specific messages
122
114
  if (error.message.includes("doesn't belong to the targeted mailbox")) {
123
- return {
124
- content: [
125
- {
126
- type: 'text',
127
- text: `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`,
128
- },
129
- ],
130
- };
115
+ return toolError(
116
+ `The email ID seems invalid or doesn't belong to your mailbox. Please try with a different email ID.`
117
+ );
131
118
  } else {
132
- return {
133
- content: [
134
- {
135
- type: 'text',
136
- text: `Failed to read email: ${error.message}`,
137
- },
138
- ],
139
- };
119
+ return toolError(`Failed to read email: ${error.message}`);
140
120
  }
141
121
  }
142
122
  } catch (error) {
143
123
  if (error.message === 'Authentication required') {
144
- return {
145
- content: [
146
- {
147
- type: 'text',
148
- text: "Authentication required. Please use the 'authenticate' tool first.",
149
- },
150
- ],
151
- };
124
+ return authRequiredError();
152
125
  }
153
126
 
154
- return {
155
- content: [
156
- {
157
- type: 'text',
158
- text: `Error accessing email: ${error.message}`,
159
- },
160
- ],
161
- };
127
+ return toolError(`Error accessing email: ${error.message}`);
162
128
  }
163
129
  }
164
130