@agenticmail/enterprise 0.5.320 → 0.5.322

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.
@@ -0,0 +1,4159 @@
1
+ import {
2
+ errorResult,
3
+ jsonResult
4
+ } from "./chunk-ZB3VC2MR.js";
5
+ import "./chunk-KFQGP6VL.js";
6
+
7
+ // src/agent-tools/tools/microsoft/graph-api.ts
8
+ var GRAPH_BASE = "https://graph.microsoft.com/v1.0";
9
+ var GRAPH_BETA = "https://graph.microsoft.com/beta";
10
+ async function graph(token, path, opts) {
11
+ const method = opts?.method || "GET";
12
+ const base = opts?.beta ? GRAPH_BETA : GRAPH_BASE;
13
+ const url = new URL(base + path);
14
+ if (opts?.query) {
15
+ for (const [k, v] of Object.entries(opts.query)) {
16
+ if (v !== void 0 && v !== "") url.searchParams.set(k, v);
17
+ }
18
+ }
19
+ const headers = {
20
+ Authorization: `Bearer ${token}`,
21
+ ...opts?.headers
22
+ };
23
+ if (!opts?.rawBody && !opts?.headers?.["Content-Type"]) {
24
+ headers["Content-Type"] = "application/json";
25
+ }
26
+ const maxRetries = opts?.retries ?? 3;
27
+ let lastError = null;
28
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
29
+ try {
30
+ const res = await fetch(url.toString(), {
31
+ method,
32
+ headers,
33
+ body: opts?.rawBody || (opts?.body ? JSON.stringify(opts.body) : void 0)
34
+ });
35
+ if (res.status === 429 && attempt < maxRetries) {
36
+ const retryAfter = parseInt(res.headers.get("Retry-After") || "5", 10);
37
+ await sleep(retryAfter * 1e3);
38
+ continue;
39
+ }
40
+ if (res.status >= 500 && attempt < maxRetries) {
41
+ await sleep(Math.pow(2, attempt) * 1e3);
42
+ continue;
43
+ }
44
+ if (opts?.raw) return res;
45
+ if (!res.ok) {
46
+ const err = await res.text();
47
+ throw new Error(`Graph ${method} ${path} ${res.status}: ${err}`);
48
+ }
49
+ if (res.status === 204 || res.status === 202) return {};
50
+ const ct = res.headers.get("content-type") || "";
51
+ if (ct.includes("application/json")) return res.json();
52
+ return { content: await res.text() };
53
+ } catch (e) {
54
+ lastError = e;
55
+ if (attempt < maxRetries && e.message?.includes("fetch failed")) {
56
+ await sleep(Math.pow(2, attempt) * 1e3);
57
+ continue;
58
+ }
59
+ throw e;
60
+ }
61
+ }
62
+ throw lastError || new Error("Graph request failed after retries");
63
+ }
64
+ function sleep(ms) {
65
+ return new Promise((resolve) => setTimeout(resolve, ms));
66
+ }
67
+
68
+ // src/agent-tools/tools/microsoft/outlook-mail.ts
69
+ function mapMessage(m, full = false) {
70
+ const result = {
71
+ id: m.id,
72
+ subject: m.subject,
73
+ from: m.from?.emailAddress?.address,
74
+ fromName: m.from?.emailAddress?.name,
75
+ to: m.toRecipients?.map((r) => r.emailAddress?.address),
76
+ date: m.receivedDateTime,
77
+ isRead: m.isRead,
78
+ hasAttachments: m.hasAttachments,
79
+ importance: m.importance,
80
+ conversationId: m.conversationId
81
+ };
82
+ if (!full) {
83
+ result.preview = m.bodyPreview;
84
+ result.flagged = m.flag?.flagStatus === "flagged";
85
+ result.cc = m.ccRecipients?.map((r) => r.emailAddress?.address);
86
+ } else {
87
+ result.to = m.toRecipients?.map((r) => ({ email: r.emailAddress?.address, name: r.emailAddress?.name }));
88
+ result.cc = m.ccRecipients?.map((r) => ({ email: r.emailAddress?.address, name: r.emailAddress?.name }));
89
+ result.bcc = m.bccRecipients?.map((r) => ({ email: r.emailAddress?.address, name: r.emailAddress?.name }));
90
+ result.replyTo = m.replyTo?.map((r) => r.emailAddress?.address);
91
+ result.sentDate = m.sentDateTime;
92
+ result.body = m.body?.content;
93
+ result.bodyType = m.body?.contentType;
94
+ result.internetMessageId = m.internetMessageId;
95
+ result.categories = m.categories;
96
+ result.flagged = m.flag?.flagStatus === "flagged";
97
+ }
98
+ return result;
99
+ }
100
+ var LIST_SELECT = "id,subject,from,toRecipients,ccRecipients,receivedDateTime,isRead,hasAttachments,importance,bodyPreview,flag,conversationId,categories";
101
+ var FULL_SELECT = "id,subject,from,toRecipients,ccRecipients,bccRecipients,replyTo,receivedDateTime,sentDateTime,isRead,hasAttachments,importance,body,flag,conversationId,internetMessageId,parentFolderId,categories";
102
+ function parseRecipients(str) {
103
+ return str.split(",").map((e) => ({ emailAddress: { address: e.trim() } }));
104
+ }
105
+ function createOutlookMailTools(config, _options) {
106
+ const tp = config.tokenProvider;
107
+ return [
108
+ {
109
+ name: "outlook_mail_list",
110
+ description: "List messages from Outlook mailbox. Returns recent emails from inbox or specified folder. Supports OData filtering and search.",
111
+ category: "utility",
112
+ parameters: {
113
+ type: "object",
114
+ properties: {
115
+ folder: { type: "string", description: "Folder: inbox, sentitems, drafts, deleteditems, junkemail, archive, or folder ID (default: inbox)" },
116
+ maxResults: { type: "number", description: "Max messages to return (default: 20, max: 50)" },
117
+ filter: { type: "string", description: `OData $filter (e.g., "isRead eq false", "from/emailAddress/address eq 'user@example.com'", "hasAttachments eq true", "importance eq 'high'")` },
118
+ search: { type: "string", description: "Search query (searches subject, body, sender \u2014 natural language or KQL)" },
119
+ orderBy: { type: "string", description: 'Sort order (default: "receivedDateTime desc"). Options: receivedDateTime, from/emailAddress/address, subject, importance' }
120
+ },
121
+ required: []
122
+ },
123
+ async execute(_id, params) {
124
+ try {
125
+ const token = await tp.getAccessToken();
126
+ const folder = params.folder || "inbox";
127
+ const top = Math.min(params.maxResults || 20, 50);
128
+ const query = {
129
+ "$top": String(top),
130
+ "$orderby": params.orderBy || "receivedDateTime desc",
131
+ "$select": LIST_SELECT
132
+ };
133
+ if (params.filter) query["$filter"] = params.filter;
134
+ if (params.search) query["$search"] = `"${params.search}"`;
135
+ const data = await graph(token, `/me/mailFolders/${folder}/messages`, { query });
136
+ const messages = (data.value || []).map((m) => mapMessage(m));
137
+ return jsonResult({ messages, count: messages.length, folder });
138
+ } catch (e) {
139
+ return errorResult(e.message);
140
+ }
141
+ }
142
+ },
143
+ {
144
+ name: "outlook_mail_read",
145
+ description: "Read a specific email message by ID. Returns full body, headers, attachments list, and categories.",
146
+ category: "utility",
147
+ parameters: {
148
+ type: "object",
149
+ properties: {
150
+ messageId: { type: "string", description: "Message ID to read" },
151
+ markAsRead: { type: "boolean", description: "Mark as read when opening (default: true)" },
152
+ preferText: { type: "boolean", description: "Request plain text body instead of HTML (default: false)" }
153
+ },
154
+ required: ["messageId"]
155
+ },
156
+ async execute(_id, params) {
157
+ try {
158
+ const token = await tp.getAccessToken();
159
+ const headers = {};
160
+ if (params.preferText) headers["Prefer"] = 'outlook.body-content-type="text"';
161
+ const msg = await graph(token, `/me/messages/${params.messageId}`, {
162
+ query: { "$select": FULL_SELECT },
163
+ headers
164
+ });
165
+ if (params.markAsRead !== false && !msg.isRead) {
166
+ graph(token, `/me/messages/${params.messageId}`, { method: "PATCH", body: { isRead: true } }).catch(() => {
167
+ });
168
+ }
169
+ let attachments = [];
170
+ if (msg.hasAttachments) {
171
+ const att = await graph(token, `/me/messages/${params.messageId}/attachments`, {
172
+ query: { "$select": "id,name,contentType,size,isInline" }
173
+ });
174
+ attachments = (att.value || []).map((a) => ({
175
+ id: a.id,
176
+ name: a.name,
177
+ contentType: a.contentType,
178
+ size: a.size,
179
+ isInline: a.isInline
180
+ }));
181
+ }
182
+ const result = mapMessage(msg, true);
183
+ result.attachments = attachments;
184
+ result.attachmentCount = attachments.length;
185
+ return jsonResult(result);
186
+ } catch (e) {
187
+ return errorResult(e.message);
188
+ }
189
+ }
190
+ },
191
+ {
192
+ name: "outlook_mail_thread",
193
+ description: "Get all messages in a conversation thread. Groups related emails by conversationId for context.",
194
+ category: "utility",
195
+ parameters: {
196
+ type: "object",
197
+ properties: {
198
+ conversationId: { type: "string", description: "Conversation ID (from any message in the thread)" },
199
+ messageId: { type: "string", description: "Message ID \u2014 will auto-find its conversationId" },
200
+ maxResults: { type: "number", description: "Max messages in thread (default: 25)" }
201
+ },
202
+ required: []
203
+ },
204
+ async execute(_id, params) {
205
+ try {
206
+ const token = await tp.getAccessToken();
207
+ let convId = params.conversationId;
208
+ if (!convId && params.messageId) {
209
+ const msg = await graph(token, `/me/messages/${params.messageId}`, { query: { "$select": "conversationId" } });
210
+ convId = msg.conversationId;
211
+ }
212
+ if (!convId) throw new Error("Provide conversationId or messageId");
213
+ const data = await graph(token, "/me/messages", {
214
+ query: {
215
+ "$filter": `conversationId eq '${convId}'`,
216
+ "$orderby": "receivedDateTime asc",
217
+ "$top": String(params.maxResults || 25),
218
+ "$select": FULL_SELECT
219
+ }
220
+ });
221
+ const messages = (data.value || []).map((m) => mapMessage(m, true));
222
+ return jsonResult({ conversationId: convId, messages, count: messages.length });
223
+ } catch (e) {
224
+ return errorResult(e.message);
225
+ }
226
+ }
227
+ },
228
+ {
229
+ name: "outlook_mail_send",
230
+ description: "Send an email via Outlook. Supports to, cc, bcc, HTML body, attachments (base64), importance, and reply-to.",
231
+ category: "utility",
232
+ parameters: {
233
+ type: "object",
234
+ properties: {
235
+ to: { type: "string", description: "Recipient email(s), comma-separated" },
236
+ subject: { type: "string", description: "Email subject" },
237
+ body: { type: "string", description: "Email body (HTML or plain text)" },
238
+ cc: { type: "string", description: "CC recipients, comma-separated" },
239
+ bcc: { type: "string", description: "BCC recipients, comma-separated" },
240
+ importance: { type: "string", description: "low, normal, or high (default: normal)" },
241
+ isHtml: { type: "boolean", description: "Whether body is HTML (default: auto-detect)" },
242
+ replyTo: { type: "string", description: "Reply-to address" },
243
+ saveToSent: { type: "boolean", description: "Save to Sent Items (default: true)" },
244
+ attachments: { type: "array", description: "Array of {name, contentType, contentBytes} \u2014 contentBytes is base64" },
245
+ categories: { type: "array", items: { type: "string" }, description: "Category labels to apply" }
246
+ },
247
+ required: ["to", "subject", "body"]
248
+ },
249
+ async execute(_id, params) {
250
+ try {
251
+ const token = await tp.getAccessToken();
252
+ const isHtml = params.isHtml ?? /<[a-z][\s\S]*>/i.test(params.body);
253
+ const message = {
254
+ subject: params.subject,
255
+ body: { contentType: isHtml ? "HTML" : "Text", content: params.body },
256
+ toRecipients: parseRecipients(params.to)
257
+ };
258
+ if (params.cc) message.ccRecipients = parseRecipients(params.cc);
259
+ if (params.bcc) message.bccRecipients = parseRecipients(params.bcc);
260
+ if (params.importance) message.importance = params.importance;
261
+ if (params.replyTo) message.replyTo = [{ emailAddress: { address: params.replyTo } }];
262
+ if (params.categories) message.categories = params.categories;
263
+ if (params.attachments?.length) {
264
+ message.attachments = params.attachments.map((a) => ({
265
+ "@odata.type": "#microsoft.graph.fileAttachment",
266
+ name: a.name,
267
+ contentType: a.contentType || "application/octet-stream",
268
+ contentBytes: a.contentBytes
269
+ }));
270
+ }
271
+ await graph(token, "/me/sendMail", {
272
+ method: "POST",
273
+ body: { message, saveToSentItems: params.saveToSent !== false }
274
+ });
275
+ return jsonResult({ sent: true, to: params.to, subject: params.subject });
276
+ } catch (e) {
277
+ return errorResult(e.message);
278
+ }
279
+ }
280
+ },
281
+ {
282
+ name: "outlook_mail_reply",
283
+ description: "Reply to an email. Can reply to sender only or reply-all. Supports HTML and attachments.",
284
+ category: "utility",
285
+ parameters: {
286
+ type: "object",
287
+ properties: {
288
+ messageId: { type: "string", description: "Message ID to reply to" },
289
+ body: { type: "string", description: "Reply body text" },
290
+ replyAll: { type: "boolean", description: "Reply to all recipients (default: false)" },
291
+ isHtml: { type: "boolean", description: "Whether body is HTML (default: false)" },
292
+ attachments: { type: "array", description: "Array of {name, contentType, contentBytes}" }
293
+ },
294
+ required: ["messageId", "body"]
295
+ },
296
+ async execute(_id, params) {
297
+ try {
298
+ const token = await tp.getAccessToken();
299
+ const action = params.replyAll ? "replyAll" : "reply";
300
+ const body = { comment: params.body };
301
+ if (params.attachments?.length) {
302
+ const draft = await graph(token, `/me/messages/${params.messageId}/createReply`, { method: "POST" });
303
+ await graph(token, `/me/messages/${draft.id}`, {
304
+ method: "PATCH",
305
+ body: {
306
+ body: { contentType: params.isHtml ? "HTML" : "Text", content: params.body }
307
+ }
308
+ });
309
+ for (const a of params.attachments) {
310
+ await graph(token, `/me/messages/${draft.id}/attachments`, {
311
+ method: "POST",
312
+ body: {
313
+ "@odata.type": "#microsoft.graph.fileAttachment",
314
+ name: a.name,
315
+ contentType: a.contentType || "application/octet-stream",
316
+ contentBytes: a.contentBytes
317
+ }
318
+ });
319
+ }
320
+ await graph(token, `/me/messages/${draft.id}/send`, { method: "POST" });
321
+ return jsonResult({ replied: true, messageId: params.messageId, replyAll: !!params.replyAll, withAttachments: true });
322
+ }
323
+ await graph(token, `/me/messages/${params.messageId}/${action}`, { method: "POST", body });
324
+ return jsonResult({ replied: true, messageId: params.messageId, replyAll: !!params.replyAll });
325
+ } catch (e) {
326
+ return errorResult(e.message);
327
+ }
328
+ }
329
+ },
330
+ {
331
+ name: "outlook_mail_forward",
332
+ description: "Forward an email to another recipient with an optional comment.",
333
+ category: "utility",
334
+ parameters: {
335
+ type: "object",
336
+ properties: {
337
+ messageId: { type: "string", description: "Message ID to forward" },
338
+ to: { type: "string", description: "Forward recipient email(s), comma-separated" },
339
+ comment: { type: "string", description: "Optional comment above the forwarded message" }
340
+ },
341
+ required: ["messageId", "to"]
342
+ },
343
+ async execute(_id, params) {
344
+ try {
345
+ const token = await tp.getAccessToken();
346
+ await graph(token, `/me/messages/${params.messageId}/forward`, {
347
+ method: "POST",
348
+ body: { comment: params.comment || "", toRecipients: parseRecipients(params.to) }
349
+ });
350
+ return jsonResult({ forwarded: true, messageId: params.messageId, to: params.to });
351
+ } catch (e) {
352
+ return errorResult(e.message);
353
+ }
354
+ }
355
+ },
356
+ {
357
+ name: "outlook_mail_move",
358
+ description: "Move a message to another folder.",
359
+ category: "utility",
360
+ parameters: {
361
+ type: "object",
362
+ properties: {
363
+ messageId: { type: "string", description: "Message ID to move" },
364
+ folder: { type: "string", description: "Destination: inbox, archive, deleteditems, junkemail, sentitems, drafts, or folder ID" }
365
+ },
366
+ required: ["messageId", "folder"]
367
+ },
368
+ async execute(_id, params) {
369
+ try {
370
+ const token = await tp.getAccessToken();
371
+ const folderMap = {
372
+ inbox: "inbox",
373
+ archive: "archive",
374
+ trash: "deleteditems",
375
+ deleteditems: "deleteditems",
376
+ junk: "junkemail",
377
+ junkemail: "junkemail",
378
+ sent: "sentitems",
379
+ sentitems: "sentitems",
380
+ drafts: "drafts"
381
+ };
382
+ const destId = folderMap[params.folder.toLowerCase()] || params.folder;
383
+ const result = await graph(token, `/me/messages/${params.messageId}/move`, {
384
+ method: "POST",
385
+ body: { destinationId: destId }
386
+ });
387
+ return jsonResult({ moved: true, newId: result.id, folder: params.folder });
388
+ } catch (e) {
389
+ return errorResult(e.message);
390
+ }
391
+ }
392
+ },
393
+ {
394
+ name: "outlook_mail_delete",
395
+ description: "Delete an email message (moves to Deleted Items).",
396
+ category: "utility",
397
+ parameters: {
398
+ type: "object",
399
+ properties: {
400
+ messageId: { type: "string", description: "Message ID to delete" }
401
+ },
402
+ required: ["messageId"]
403
+ },
404
+ async execute(_id, params) {
405
+ try {
406
+ const token = await tp.getAccessToken();
407
+ await graph(token, `/me/messages/${params.messageId}`, { method: "DELETE" });
408
+ return jsonResult({ deleted: true, messageId: params.messageId });
409
+ } catch (e) {
410
+ return errorResult(e.message);
411
+ }
412
+ }
413
+ },
414
+ {
415
+ name: "outlook_mail_update",
416
+ description: "Update message properties: read/unread, flag, importance, categories.",
417
+ category: "utility",
418
+ parameters: {
419
+ type: "object",
420
+ properties: {
421
+ messageId: { type: "string", description: "Message ID to update" },
422
+ isRead: { type: "boolean", description: "Mark as read or unread" },
423
+ importance: { type: "string", description: "low, normal, or high" },
424
+ flag: { type: "string", description: "notFlagged, flagged, or complete" },
425
+ categories: { type: "array", items: { type: "string" }, description: "Category labels to set (replaces existing)" }
426
+ },
427
+ required: ["messageId"]
428
+ },
429
+ async execute(_id, params) {
430
+ try {
431
+ const token = await tp.getAccessToken();
432
+ const body = {};
433
+ if (params.isRead !== void 0) body.isRead = params.isRead;
434
+ if (params.importance) body.importance = params.importance;
435
+ if (params.flag) body.flag = { flagStatus: params.flag };
436
+ if (params.categories) body.categories = params.categories;
437
+ await graph(token, `/me/messages/${params.messageId}`, { method: "PATCH", body });
438
+ return jsonResult({ updated: true, messageId: params.messageId });
439
+ } catch (e) {
440
+ return errorResult(e.message);
441
+ }
442
+ }
443
+ },
444
+ {
445
+ name: "outlook_mail_search",
446
+ description: "Search emails across all folders. Supports natural language and KQL syntax.",
447
+ category: "utility",
448
+ parameters: {
449
+ type: "object",
450
+ properties: {
451
+ query: { type: "string", description: 'Search query (natural language or KQL: "from:user@example.com subject:report has:attachment")' },
452
+ maxResults: { type: "number", description: "Max results (default: 15, max: 25)" },
453
+ folder: { type: "string", description: "Limit search to specific folder (default: all folders)" }
454
+ },
455
+ required: ["query"]
456
+ },
457
+ async execute(_id, params) {
458
+ try {
459
+ const token = await tp.getAccessToken();
460
+ const top = Math.min(params.maxResults || 15, 25);
461
+ const basePath = params.folder ? `/me/mailFolders/${params.folder}/messages` : "/me/messages";
462
+ const data = await graph(token, basePath, {
463
+ query: {
464
+ "$search": `"${params.query}"`,
465
+ "$top": String(top),
466
+ "$select": LIST_SELECT
467
+ }
468
+ });
469
+ const messages = (data.value || []).map((m) => mapMessage(m));
470
+ return jsonResult({ messages, count: messages.length, query: params.query });
471
+ } catch (e) {
472
+ return errorResult(e.message);
473
+ }
474
+ }
475
+ },
476
+ {
477
+ name: "outlook_mail_draft",
478
+ description: "Create a draft email. Can be edited and sent later with outlook_mail_send_draft.",
479
+ category: "utility",
480
+ parameters: {
481
+ type: "object",
482
+ properties: {
483
+ to: { type: "string", description: "Recipient email(s), comma-separated" },
484
+ subject: { type: "string", description: "Email subject" },
485
+ body: { type: "string", description: "Email body" },
486
+ cc: { type: "string", description: "CC recipients" },
487
+ isHtml: { type: "boolean", description: "Whether body is HTML" },
488
+ importance: { type: "string", description: "low, normal, or high" }
489
+ },
490
+ required: ["to", "subject", "body"]
491
+ },
492
+ async execute(_id, params) {
493
+ try {
494
+ const token = await tp.getAccessToken();
495
+ const message = {
496
+ subject: params.subject,
497
+ body: { contentType: params.isHtml ? "HTML" : "Text", content: params.body },
498
+ toRecipients: parseRecipients(params.to)
499
+ };
500
+ if (params.cc) message.ccRecipients = parseRecipients(params.cc);
501
+ if (params.importance) message.importance = params.importance;
502
+ const draft = await graph(token, "/me/messages", { method: "POST", body: message });
503
+ return jsonResult({ draftId: draft.id, subject: params.subject, status: "draft_created" });
504
+ } catch (e) {
505
+ return errorResult(e.message);
506
+ }
507
+ }
508
+ },
509
+ {
510
+ name: "outlook_mail_send_draft",
511
+ description: "Send an existing draft email.",
512
+ category: "utility",
513
+ parameters: {
514
+ type: "object",
515
+ properties: {
516
+ messageId: { type: "string", description: "Draft message ID to send" }
517
+ },
518
+ required: ["messageId"]
519
+ },
520
+ async execute(_id, params) {
521
+ try {
522
+ const token = await tp.getAccessToken();
523
+ await graph(token, `/me/messages/${params.messageId}/send`, { method: "POST" });
524
+ return jsonResult({ sent: true, messageId: params.messageId });
525
+ } catch (e) {
526
+ return errorResult(e.message);
527
+ }
528
+ }
529
+ },
530
+ {
531
+ name: "outlook_mail_folders",
532
+ description: "List all mail folders including nested child folders.",
533
+ category: "utility",
534
+ parameters: {
535
+ type: "object",
536
+ properties: {
537
+ parentFolderId: { type: "string", description: "Parent folder ID to list children (omit for top-level)" },
538
+ includeHidden: { type: "boolean", description: "Include hidden folders (default: false)" }
539
+ },
540
+ required: []
541
+ },
542
+ async execute(_id, params) {
543
+ try {
544
+ const token = await tp.getAccessToken();
545
+ const path = params.parentFolderId ? `/me/mailFolders/${params.parentFolderId}/childFolders` : "/me/mailFolders";
546
+ const query = {
547
+ "$top": "100",
548
+ "$select": "id,displayName,totalItemCount,unreadItemCount,parentFolderId,isHidden,childFolderCount"
549
+ };
550
+ if (!params.includeHidden) query["$filter"] = "isHidden eq false";
551
+ const data = await graph(token, path, { query });
552
+ const folders = (data.value || []).map((f) => ({
553
+ id: f.id,
554
+ name: f.displayName,
555
+ totalItems: f.totalItemCount,
556
+ unreadItems: f.unreadItemCount,
557
+ childFolders: f.childFolderCount,
558
+ isHidden: f.isHidden
559
+ }));
560
+ return jsonResult({ folders, count: folders.length });
561
+ } catch (e) {
562
+ return errorResult(e.message);
563
+ }
564
+ }
565
+ },
566
+ {
567
+ name: "outlook_mail_create_folder",
568
+ description: "Create a new mail folder.",
569
+ category: "utility",
570
+ parameters: {
571
+ type: "object",
572
+ properties: {
573
+ name: { type: "string", description: "Folder name" },
574
+ parentFolderId: { type: "string", description: "Parent folder ID (omit for top-level)" }
575
+ },
576
+ required: ["name"]
577
+ },
578
+ async execute(_id, params) {
579
+ try {
580
+ const token = await tp.getAccessToken();
581
+ const path = params.parentFolderId ? `/me/mailFolders/${params.parentFolderId}/childFolders` : "/me/mailFolders";
582
+ const folder = await graph(token, path, {
583
+ method: "POST",
584
+ body: { displayName: params.name }
585
+ });
586
+ return jsonResult({ id: folder.id, name: folder.displayName });
587
+ } catch (e) {
588
+ return errorResult(e.message);
589
+ }
590
+ }
591
+ },
592
+ {
593
+ name: "outlook_mail_attachment_download",
594
+ description: "Download an attachment from an email. Returns content as base64.",
595
+ category: "utility",
596
+ parameters: {
597
+ type: "object",
598
+ properties: {
599
+ messageId: { type: "string", description: "Message ID containing the attachment" },
600
+ attachmentId: { type: "string", description: "Attachment ID to download" }
601
+ },
602
+ required: ["messageId", "attachmentId"]
603
+ },
604
+ async execute(_id, params) {
605
+ try {
606
+ const token = await tp.getAccessToken();
607
+ const att = await graph(token, `/me/messages/${params.messageId}/attachments/${params.attachmentId}`);
608
+ return jsonResult({
609
+ name: att.name,
610
+ contentType: att.contentType,
611
+ size: att.size,
612
+ content: att.contentBytes
613
+ });
614
+ } catch (e) {
615
+ return errorResult(e.message);
616
+ }
617
+ }
618
+ },
619
+ {
620
+ name: "outlook_mail_auto_reply",
621
+ description: "Configure automatic replies (out-of-office / vacation responder).",
622
+ category: "utility",
623
+ parameters: {
624
+ type: "object",
625
+ properties: {
626
+ status: { type: "string", description: "disabled, alwaysEnabled, or scheduled" },
627
+ internalMessage: { type: "string", description: "Auto-reply message for internal senders (HTML)" },
628
+ externalMessage: { type: "string", description: "Auto-reply message for external senders (HTML)" },
629
+ externalAudience: { type: "string", description: "none, contactsOnly, or all (default: all)" },
630
+ startDate: { type: "string", description: "Start date (ISO 8601, required if scheduled)" },
631
+ endDate: { type: "string", description: "End date (ISO 8601, required if scheduled)" }
632
+ },
633
+ required: ["status"]
634
+ },
635
+ async execute(_id, params) {
636
+ try {
637
+ const token = await tp.getAccessToken();
638
+ if (params.status === "disabled") {
639
+ await graph(token, "/me/mailboxSettings", {
640
+ method: "PATCH",
641
+ body: { automaticRepliesSetting: { status: "disabled" } }
642
+ });
643
+ return jsonResult({ autoReply: "disabled" });
644
+ }
645
+ const setting = { status: params.status };
646
+ if (params.internalMessage) setting.internalReplyMessage = params.internalMessage;
647
+ if (params.externalMessage) setting.externalReplyMessage = params.externalMessage;
648
+ if (params.externalAudience) setting.externalAudience = params.externalAudience;
649
+ if (params.status === "scheduled") {
650
+ setting.scheduledStartDateTime = { dateTime: params.startDate, timeZone: "UTC" };
651
+ setting.scheduledEndDateTime = { dateTime: params.endDate, timeZone: "UTC" };
652
+ }
653
+ await graph(token, "/me/mailboxSettings", {
654
+ method: "PATCH",
655
+ body: { automaticRepliesSetting: setting }
656
+ });
657
+ return jsonResult({ autoReply: params.status, configured: true });
658
+ } catch (e) {
659
+ return errorResult(e.message);
660
+ }
661
+ }
662
+ },
663
+ {
664
+ name: "outlook_mail_get_auto_reply",
665
+ description: "Get current automatic reply (out-of-office) settings.",
666
+ category: "utility",
667
+ parameters: { type: "object", properties: {}, required: [] },
668
+ async execute(_id) {
669
+ try {
670
+ const token = await tp.getAccessToken();
671
+ const settings = await graph(token, "/me/mailboxSettings/automaticRepliesSetting");
672
+ return jsonResult({
673
+ status: settings.status,
674
+ internalMessage: settings.internalReplyMessage,
675
+ externalMessage: settings.externalReplyMessage,
676
+ externalAudience: settings.externalAudience,
677
+ startDate: settings.scheduledStartDateTime?.dateTime,
678
+ endDate: settings.scheduledEndDateTime?.dateTime
679
+ });
680
+ } catch (e) {
681
+ return errorResult(e.message);
682
+ }
683
+ }
684
+ },
685
+ {
686
+ name: "outlook_mail_rules",
687
+ description: "List, create, or delete inbox rules (message rules). Rules auto-process incoming mail.",
688
+ category: "utility",
689
+ parameters: {
690
+ type: "object",
691
+ properties: {
692
+ action: { type: "string", description: "list, create, or delete" },
693
+ ruleId: { type: "string", description: "Rule ID (for delete)" },
694
+ displayName: { type: "string", description: "Rule name (for create)" },
695
+ conditions: { type: "object", description: 'Match conditions object \u2014 e.g., {fromAddresses:[{emailAddress:{address:"user@example.com"}}], subjectContains:["invoice"]}' },
696
+ actions: { type: "object", description: 'Actions when matched \u2014 e.g., {moveToFolder:"archive", markAsRead:true, stopProcessingRules:true}' },
697
+ isEnabled: { type: "boolean", description: "Enable/disable rule (default: true)" }
698
+ },
699
+ required: ["action"]
700
+ },
701
+ async execute(_id, params) {
702
+ try {
703
+ const token = await tp.getAccessToken();
704
+ if (params.action === "list") {
705
+ const data = await graph(token, "/me/mailFolders/inbox/messageRules");
706
+ const rules = (data.value || []).map((r) => ({
707
+ id: r.id,
708
+ name: r.displayName,
709
+ isEnabled: r.isEnabled,
710
+ sequence: r.sequence,
711
+ conditions: r.conditions,
712
+ actions: r.actions
713
+ }));
714
+ return jsonResult({ rules, count: rules.length });
715
+ }
716
+ if (params.action === "delete") {
717
+ await graph(token, `/me/mailFolders/inbox/messageRules/${params.ruleId}`, { method: "DELETE" });
718
+ return jsonResult({ deleted: true, ruleId: params.ruleId });
719
+ }
720
+ if (params.action === "create") {
721
+ const rule = await graph(token, "/me/mailFolders/inbox/messageRules", {
722
+ method: "POST",
723
+ body: {
724
+ displayName: params.displayName || "Agent Rule",
725
+ isEnabled: params.isEnabled !== false,
726
+ conditions: params.conditions || {},
727
+ actions: params.actions || {}
728
+ }
729
+ });
730
+ return jsonResult({ id: rule.id, name: rule.displayName, created: true });
731
+ }
732
+ throw new Error("action must be list, create, or delete");
733
+ } catch (e) {
734
+ return errorResult(e.message);
735
+ }
736
+ }
737
+ },
738
+ {
739
+ name: "outlook_mail_categories",
740
+ description: "Manage Outlook categories (color-coded labels). List, create, or delete.",
741
+ category: "utility",
742
+ parameters: {
743
+ type: "object",
744
+ properties: {
745
+ action: { type: "string", description: "list, create, or delete" },
746
+ name: { type: "string", description: "Category name (for create)" },
747
+ color: { type: "string", description: "Color preset: None, Red, Orange, Brown, Yellow, Green, Teal, Olive, Blue, Purple, Cranberry, Steel, DarkSteel, Gray, DarkGray, Black (for create)" },
748
+ categoryId: { type: "string", description: "Category ID (for delete)" }
749
+ },
750
+ required: ["action"]
751
+ },
752
+ async execute(_id, params) {
753
+ try {
754
+ const token = await tp.getAccessToken();
755
+ if (params.action === "list") {
756
+ const data = await graph(token, "/me/outlook/masterCategories");
757
+ const cats = (data.value || []).map((c) => ({
758
+ id: c.id,
759
+ name: c.displayName,
760
+ color: c.color
761
+ }));
762
+ return jsonResult({ categories: cats, count: cats.length });
763
+ }
764
+ if (params.action === "create") {
765
+ const cat = await graph(token, "/me/outlook/masterCategories", {
766
+ method: "POST",
767
+ body: { displayName: params.name, color: params.color || "None" }
768
+ });
769
+ return jsonResult({ id: cat.id, name: cat.displayName, color: cat.color });
770
+ }
771
+ if (params.action === "delete") {
772
+ await graph(token, `/me/outlook/masterCategories/${params.categoryId}`, { method: "DELETE" });
773
+ return jsonResult({ deleted: true });
774
+ }
775
+ throw new Error("action must be list, create, or delete");
776
+ } catch (e) {
777
+ return errorResult(e.message);
778
+ }
779
+ }
780
+ },
781
+ {
782
+ name: "outlook_mail_profile",
783
+ description: "Get the mailbox user profile \u2014 email address, display name, timezone, language, and mailbox settings.",
784
+ category: "utility",
785
+ parameters: { type: "object", properties: {}, required: [] },
786
+ async execute(_id) {
787
+ try {
788
+ const token = await tp.getAccessToken();
789
+ const [user, settings] = await Promise.all([
790
+ graph(token, "/me", { query: { "$select": "displayName,mail,userPrincipalName,jobTitle,department,officeLocation" } }),
791
+ graph(token, "/me/mailboxSettings")
792
+ ]);
793
+ return jsonResult({
794
+ email: user.mail || user.userPrincipalName,
795
+ name: user.displayName,
796
+ jobTitle: user.jobTitle,
797
+ department: user.department,
798
+ office: user.officeLocation,
799
+ timeZone: settings.timeZone,
800
+ language: settings.language?.locale,
801
+ dateFormat: settings.dateFormat,
802
+ timeFormat: settings.timeFormat,
803
+ delegateMeetingRequests: settings.delegateMeetingMessageDeliveryOptions
804
+ });
805
+ } catch (e) {
806
+ return errorResult(e.message);
807
+ }
808
+ }
809
+ }
810
+ ];
811
+ }
812
+
813
+ // src/agent-tools/tools/microsoft/outlook-calendar.ts
814
+ function createOutlookCalendarTools(config, _options) {
815
+ const tp = config.tokenProvider;
816
+ return [
817
+ {
818
+ name: "outlook_calendar_list",
819
+ description: "List all calendars the agent has access to.",
820
+ category: "utility",
821
+ parameters: { type: "object", properties: {}, required: [] },
822
+ async execute(_id) {
823
+ try {
824
+ const token = await tp.getAccessToken();
825
+ const data = await graph(token, "/me/calendars");
826
+ const cals = (data.value || []).map((c) => ({
827
+ id: c.id,
828
+ name: c.name,
829
+ color: c.color,
830
+ isDefaultCalendar: c.isDefaultCalendar,
831
+ canEdit: c.canEdit,
832
+ owner: c.owner?.address
833
+ }));
834
+ return jsonResult({ calendars: cals, count: cals.length });
835
+ } catch (e) {
836
+ return errorResult(e.message);
837
+ }
838
+ }
839
+ },
840
+ {
841
+ name: "outlook_calendar_events",
842
+ description: "List upcoming events from a calendar. Defaults to the primary calendar.",
843
+ category: "utility",
844
+ parameters: {
845
+ type: "object",
846
+ properties: {
847
+ calendarId: { type: "string", description: "Calendar ID (omit for default calendar)" },
848
+ timeMin: { type: "string", description: "Start of time range (ISO 8601, default: now)" },
849
+ timeMax: { type: "string", description: "End of time range (ISO 8601, default: +7 days)" },
850
+ maxResults: { type: "number", description: "Max events (default: 20)" }
851
+ },
852
+ required: []
853
+ },
854
+ async execute(_id, params) {
855
+ try {
856
+ const token = await tp.getAccessToken();
857
+ const now = /* @__PURE__ */ new Date();
858
+ const timeMin = params.timeMin || now.toISOString();
859
+ const weekLater = new Date(now.getTime() + 7 * 864e5);
860
+ const timeMax = params.timeMax || weekLater.toISOString();
861
+ const top = params.maxResults || 20;
862
+ const basePath = params.calendarId ? `/me/calendars/${params.calendarId}/calendarView` : "/me/calendarView";
863
+ const data = await graph(token, basePath, {
864
+ query: {
865
+ startDateTime: timeMin,
866
+ endDateTime: timeMax,
867
+ "$top": String(top),
868
+ "$orderby": "start/dateTime",
869
+ "$select": "id,subject,start,end,location,organizer,attendees,isAllDay,isCancelled,showAs,importance,bodyPreview,onlineMeeting,webLink"
870
+ }
871
+ });
872
+ const events = (data.value || []).map((e) => ({
873
+ id: e.id,
874
+ subject: e.subject,
875
+ start: e.start?.dateTime,
876
+ startTimeZone: e.start?.timeZone,
877
+ end: e.end?.dateTime,
878
+ endTimeZone: e.end?.timeZone,
879
+ isAllDay: e.isAllDay,
880
+ location: e.location?.displayName,
881
+ organizer: e.organizer?.emailAddress?.address,
882
+ attendees: e.attendees?.map((a) => ({
883
+ email: a.emailAddress?.address,
884
+ name: a.emailAddress?.name,
885
+ status: a.status?.response,
886
+ type: a.type
887
+ })),
888
+ showAs: e.showAs,
889
+ isCancelled: e.isCancelled,
890
+ preview: e.bodyPreview,
891
+ meetingUrl: e.onlineMeeting?.joinUrl,
892
+ webLink: e.webLink
893
+ }));
894
+ return jsonResult({ events, count: events.length });
895
+ } catch (e) {
896
+ return errorResult(e.message);
897
+ }
898
+ }
899
+ },
900
+ {
901
+ name: "outlook_calendar_create",
902
+ description: "Create a new calendar event. Supports attendees, location, online meeting (Teams), and recurrence.",
903
+ category: "utility",
904
+ parameters: {
905
+ type: "object",
906
+ properties: {
907
+ subject: { type: "string", description: "Event subject/title" },
908
+ start: { type: "string", description: "Start time (ISO 8601)" },
909
+ end: { type: "string", description: "End time (ISO 8601)" },
910
+ timeZone: { type: "string", description: "Time zone (default: UTC)" },
911
+ location: { type: "string", description: "Location name" },
912
+ body: { type: "string", description: "Event body/description" },
913
+ attendees: { type: "string", description: "Attendee emails, comma-separated" },
914
+ isOnlineMeeting: { type: "boolean", description: "Create a Teams meeting link (default: false)" },
915
+ isAllDay: { type: "boolean", description: "All-day event" },
916
+ importance: { type: "string", description: "low, normal, or high" },
917
+ reminderMinutes: { type: "number", description: "Reminder before event in minutes (default: 15)" },
918
+ calendarId: { type: "string", description: "Calendar ID (omit for default)" }
919
+ },
920
+ required: ["subject", "start", "end"]
921
+ },
922
+ async execute(_id, params) {
923
+ try {
924
+ const token = await tp.getAccessToken();
925
+ const tz = params.timeZone || "UTC";
926
+ const event = {
927
+ subject: params.subject,
928
+ start: { dateTime: params.start, timeZone: tz },
929
+ end: { dateTime: params.end, timeZone: tz }
930
+ };
931
+ if (params.location) event.location = { displayName: params.location };
932
+ if (params.body) event.body = { contentType: "HTML", content: params.body };
933
+ if (params.attendees) {
934
+ event.attendees = params.attendees.split(",").map((e) => ({
935
+ emailAddress: { address: e.trim() },
936
+ type: "required"
937
+ }));
938
+ }
939
+ if (params.isOnlineMeeting) {
940
+ event.isOnlineMeeting = true;
941
+ event.onlineMeetingProvider = "teamsForBusiness";
942
+ }
943
+ if (params.isAllDay) event.isAllDay = true;
944
+ if (params.importance) event.importance = params.importance;
945
+ if (params.reminderMinutes !== void 0) {
946
+ event.isReminderOn = true;
947
+ event.reminderMinutesBefore = params.reminderMinutes;
948
+ }
949
+ const basePath = params.calendarId ? `/me/calendars/${params.calendarId}/events` : "/me/events";
950
+ const created = await graph(token, basePath, { method: "POST", body: event });
951
+ return jsonResult({
952
+ id: created.id,
953
+ subject: created.subject,
954
+ start: created.start?.dateTime,
955
+ end: created.end?.dateTime,
956
+ meetingUrl: created.onlineMeeting?.joinUrl,
957
+ webLink: created.webLink
958
+ });
959
+ } catch (e) {
960
+ return errorResult(e.message);
961
+ }
962
+ }
963
+ },
964
+ {
965
+ name: "outlook_calendar_update",
966
+ description: "Update an existing calendar event.",
967
+ category: "utility",
968
+ parameters: {
969
+ type: "object",
970
+ properties: {
971
+ eventId: { type: "string", description: "Event ID to update" },
972
+ subject: { type: "string", description: "New subject" },
973
+ start: { type: "string", description: "New start time (ISO 8601)" },
974
+ end: { type: "string", description: "New end time (ISO 8601)" },
975
+ timeZone: { type: "string", description: "Time zone" },
976
+ location: { type: "string", description: "New location" },
977
+ body: { type: "string", description: "New body content" }
978
+ },
979
+ required: ["eventId"]
980
+ },
981
+ async execute(_id, params) {
982
+ try {
983
+ const token = await tp.getAccessToken();
984
+ const update = {};
985
+ if (params.subject) update.subject = params.subject;
986
+ if (params.start) update.start = { dateTime: params.start, timeZone: params.timeZone || "UTC" };
987
+ if (params.end) update.end = { dateTime: params.end, timeZone: params.timeZone || "UTC" };
988
+ if (params.location) update.location = { displayName: params.location };
989
+ if (params.body) update.body = { contentType: "HTML", content: params.body };
990
+ const updated = await graph(token, `/me/events/${params.eventId}`, { method: "PATCH", body: update });
991
+ return jsonResult({ id: updated.id, subject: updated.subject, updated: true });
992
+ } catch (e) {
993
+ return errorResult(e.message);
994
+ }
995
+ }
996
+ },
997
+ {
998
+ name: "outlook_calendar_delete",
999
+ description: "Delete a calendar event.",
1000
+ category: "utility",
1001
+ parameters: {
1002
+ type: "object",
1003
+ properties: {
1004
+ eventId: { type: "string", description: "Event ID to delete" }
1005
+ },
1006
+ required: ["eventId"]
1007
+ },
1008
+ async execute(_id, params) {
1009
+ try {
1010
+ const token = await tp.getAccessToken();
1011
+ await graph(token, `/me/events/${params.eventId}`, { method: "DELETE" });
1012
+ return jsonResult({ deleted: true, eventId: params.eventId });
1013
+ } catch (e) {
1014
+ return errorResult(e.message);
1015
+ }
1016
+ }
1017
+ },
1018
+ {
1019
+ name: "outlook_calendar_respond",
1020
+ description: "Respond to a calendar event invitation (accept, tentatively accept, or decline).",
1021
+ category: "utility",
1022
+ parameters: {
1023
+ type: "object",
1024
+ properties: {
1025
+ eventId: { type: "string", description: "Event ID to respond to" },
1026
+ response: { type: "string", description: "accept, tentativelyAccept, or decline" },
1027
+ comment: { type: "string", description: "Optional response message" },
1028
+ sendResponse: { type: "boolean", description: "Send response to organizer (default: true)" }
1029
+ },
1030
+ required: ["eventId", "response"]
1031
+ },
1032
+ async execute(_id, params) {
1033
+ try {
1034
+ const token = await tp.getAccessToken();
1035
+ const body = { sendResponse: params.sendResponse !== false };
1036
+ if (params.comment) body.comment = params.comment;
1037
+ await graph(token, `/me/events/${params.eventId}/${params.response}`, { method: "POST", body });
1038
+ return jsonResult({ responded: true, eventId: params.eventId, response: params.response });
1039
+ } catch (e) {
1040
+ return errorResult(e.message);
1041
+ }
1042
+ }
1043
+ },
1044
+ {
1045
+ name: "outlook_calendar_freebusy",
1046
+ description: "Check free/busy availability for one or more users.",
1047
+ category: "utility",
1048
+ parameters: {
1049
+ type: "object",
1050
+ properties: {
1051
+ emails: { type: "string", description: "Email addresses to check, comma-separated" },
1052
+ start: { type: "string", description: "Start of range (ISO 8601)" },
1053
+ end: { type: "string", description: "End of range (ISO 8601)" },
1054
+ timeZone: { type: "string", description: "Time zone (default: UTC)" }
1055
+ },
1056
+ required: ["emails", "start", "end"]
1057
+ },
1058
+ async execute(_id, params) {
1059
+ try {
1060
+ const token = await tp.getAccessToken();
1061
+ const schedules = params.emails.split(",").map((e) => e.trim());
1062
+ const data = await graph(token, "/me/calendar/getSchedule", {
1063
+ method: "POST",
1064
+ body: {
1065
+ schedules,
1066
+ startTime: { dateTime: params.start, timeZone: params.timeZone || "UTC" },
1067
+ endTime: { dateTime: params.end, timeZone: params.timeZone || "UTC" },
1068
+ availabilityViewInterval: 30
1069
+ }
1070
+ });
1071
+ const results = (data.value || []).map((s) => ({
1072
+ email: s.scheduleId,
1073
+ availability: s.availabilityView,
1074
+ items: s.scheduleItems?.map((i) => ({
1075
+ status: i.status,
1076
+ subject: i.subject,
1077
+ start: i.start?.dateTime,
1078
+ end: i.end?.dateTime
1079
+ }))
1080
+ }));
1081
+ return jsonResult({ schedules: results });
1082
+ } catch (e) {
1083
+ return errorResult(e.message);
1084
+ }
1085
+ }
1086
+ }
1087
+ ];
1088
+ }
1089
+
1090
+ // src/agent-tools/tools/microsoft/onedrive.ts
1091
+ function createOneDriveTools(config, _options) {
1092
+ const tp = config.tokenProvider;
1093
+ return [
1094
+ {
1095
+ name: "onedrive_list",
1096
+ description: "List files and folders in OneDrive. Defaults to root folder.",
1097
+ category: "utility",
1098
+ parameters: {
1099
+ type: "object",
1100
+ properties: {
1101
+ path: { type: "string", description: 'Folder path (e.g., "/Documents/Reports") or item ID. Default: root' },
1102
+ maxResults: { type: "number", description: "Max items to return (default: 50)" }
1103
+ },
1104
+ required: []
1105
+ },
1106
+ async execute(_id, params) {
1107
+ try {
1108
+ const token = await tp.getAccessToken();
1109
+ const top = params.maxResults || 50;
1110
+ const basePath = params.path ? `/me/drive/root:${params.path}:/children` : "/me/drive/root/children";
1111
+ const data = await graph(token, basePath, {
1112
+ query: { "$top": String(top), "$select": "id,name,size,createdDateTime,lastModifiedDateTime,webUrl,folder,file,parentReference" }
1113
+ });
1114
+ const items = (data.value || []).map((i) => ({
1115
+ id: i.id,
1116
+ name: i.name,
1117
+ size: i.size,
1118
+ type: i.folder ? "folder" : "file",
1119
+ mimeType: i.file?.mimeType,
1120
+ childCount: i.folder?.childCount,
1121
+ created: i.createdDateTime,
1122
+ modified: i.lastModifiedDateTime,
1123
+ webUrl: i.webUrl,
1124
+ path: i.parentReference?.path
1125
+ }));
1126
+ return jsonResult({ items, count: items.length });
1127
+ } catch (e) {
1128
+ return errorResult(e.message);
1129
+ }
1130
+ }
1131
+ },
1132
+ {
1133
+ name: "onedrive_search",
1134
+ description: "Search for files in OneDrive by name or content.",
1135
+ category: "utility",
1136
+ parameters: {
1137
+ type: "object",
1138
+ properties: {
1139
+ query: { type: "string", description: "Search query" },
1140
+ maxResults: { type: "number", description: "Max results (default: 20)" }
1141
+ },
1142
+ required: ["query"]
1143
+ },
1144
+ async execute(_id, params) {
1145
+ try {
1146
+ const token = await tp.getAccessToken();
1147
+ const top = params.maxResults || 20;
1148
+ const data = await graph(token, `/me/drive/root/search(q='${encodeURIComponent(params.query)}')`, {
1149
+ query: { "$top": String(top), "$select": "id,name,size,webUrl,file,folder,lastModifiedDateTime,parentReference" }
1150
+ });
1151
+ const items = (data.value || []).map((i) => ({
1152
+ id: i.id,
1153
+ name: i.name,
1154
+ size: i.size,
1155
+ type: i.folder ? "folder" : "file",
1156
+ mimeType: i.file?.mimeType,
1157
+ modified: i.lastModifiedDateTime,
1158
+ webUrl: i.webUrl,
1159
+ path: i.parentReference?.path
1160
+ }));
1161
+ return jsonResult({ items, count: items.length, query: params.query });
1162
+ } catch (e) {
1163
+ return errorResult(e.message);
1164
+ }
1165
+ }
1166
+ },
1167
+ {
1168
+ name: "onedrive_read",
1169
+ description: "Read/download a file from OneDrive. For text files returns content; for binary returns download URL.",
1170
+ category: "utility",
1171
+ parameters: {
1172
+ type: "object",
1173
+ properties: {
1174
+ itemId: { type: "string", description: "File item ID" },
1175
+ path: { type: "string", description: 'File path (alternative to itemId, e.g., "/Documents/report.txt")' }
1176
+ },
1177
+ required: []
1178
+ },
1179
+ async execute(_id, params) {
1180
+ try {
1181
+ const token = await tp.getAccessToken();
1182
+ const itemPath4 = params.itemId ? `/me/drive/items/${params.itemId}` : `/me/drive/root:${params.path}:`;
1183
+ const meta = await graph(token, itemPath4, {
1184
+ query: { "$select": "id,name,size,file,webUrl,@microsoft.graph.downloadUrl" }
1185
+ });
1186
+ const isText = meta.file?.mimeType?.startsWith("text/") || /\.(txt|md|csv|json|xml|html|css|js|ts|py|rb|go|rs|yaml|yml|toml|ini|cfg|log|sh|bat|ps1|sql)$/i.test(meta.name || "");
1187
+ if (isText && meta.size < 1048576) {
1188
+ const contentRes = await fetch(meta["@microsoft.graph.downloadUrl"] || `https://graph.microsoft.com/v1.0${itemPath4}/content`, {
1189
+ headers: { Authorization: `Bearer ${token}` }
1190
+ });
1191
+ const content = await contentRes.text();
1192
+ return jsonResult({ id: meta.id, name: meta.name, size: meta.size, content });
1193
+ }
1194
+ return jsonResult({
1195
+ id: meta.id,
1196
+ name: meta.name,
1197
+ size: meta.size,
1198
+ mimeType: meta.file?.mimeType,
1199
+ downloadUrl: meta["@microsoft.graph.downloadUrl"],
1200
+ webUrl: meta.webUrl
1201
+ });
1202
+ } catch (e) {
1203
+ return errorResult(e.message);
1204
+ }
1205
+ }
1206
+ },
1207
+ {
1208
+ name: "onedrive_upload",
1209
+ description: "Upload a text file to OneDrive. For small files (< 4MB).",
1210
+ category: "utility",
1211
+ parameters: {
1212
+ type: "object",
1213
+ properties: {
1214
+ path: { type: "string", description: 'Destination path (e.g., "/Documents/report.md")' },
1215
+ content: { type: "string", description: "File content (text)" },
1216
+ conflictBehavior: { type: "string", description: "rename, replace, or fail (default: replace)" }
1217
+ },
1218
+ required: ["path", "content"]
1219
+ },
1220
+ async execute(_id, params) {
1221
+ try {
1222
+ const token = await tp.getAccessToken();
1223
+ const conflict = params.conflictBehavior || "replace";
1224
+ const res = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:${params.path}:/content?@microsoft.graph.conflictBehavior=${conflict}`, {
1225
+ method: "PUT",
1226
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "text/plain" },
1227
+ body: params.content
1228
+ });
1229
+ if (!res.ok) throw new Error(`Upload failed: ${res.status} ${await res.text()}`);
1230
+ const data = await res.json();
1231
+ return jsonResult({ id: data.id, name: data.name, size: data.size, webUrl: data.webUrl });
1232
+ } catch (e) {
1233
+ return errorResult(e.message);
1234
+ }
1235
+ }
1236
+ },
1237
+ {
1238
+ name: "onedrive_create_folder",
1239
+ description: "Create a new folder in OneDrive.",
1240
+ category: "utility",
1241
+ parameters: {
1242
+ type: "object",
1243
+ properties: {
1244
+ name: { type: "string", description: "Folder name" },
1245
+ parentPath: { type: "string", description: "Parent folder path (default: root)" }
1246
+ },
1247
+ required: ["name"]
1248
+ },
1249
+ async execute(_id, params) {
1250
+ try {
1251
+ const token = await tp.getAccessToken();
1252
+ const parentPath = params.parentPath ? `/me/drive/root:${params.parentPath}:/children` : "/me/drive/root/children";
1253
+ const folder = await graph(token, parentPath, {
1254
+ method: "POST",
1255
+ body: { name: params.name, folder: {}, "@microsoft.graph.conflictBehavior": "rename" }
1256
+ });
1257
+ return jsonResult({ id: folder.id, name: folder.name, webUrl: folder.webUrl });
1258
+ } catch (e) {
1259
+ return errorResult(e.message);
1260
+ }
1261
+ }
1262
+ },
1263
+ {
1264
+ name: "onedrive_delete",
1265
+ description: "Delete a file or folder from OneDrive.",
1266
+ category: "utility",
1267
+ parameters: {
1268
+ type: "object",
1269
+ properties: {
1270
+ itemId: { type: "string", description: "Item ID to delete" }
1271
+ },
1272
+ required: ["itemId"]
1273
+ },
1274
+ async execute(_id, params) {
1275
+ try {
1276
+ const token = await tp.getAccessToken();
1277
+ await graph(token, `/me/drive/items/${params.itemId}`, { method: "DELETE" });
1278
+ return jsonResult({ deleted: true, itemId: params.itemId });
1279
+ } catch (e) {
1280
+ return errorResult(e.message);
1281
+ }
1282
+ }
1283
+ },
1284
+ {
1285
+ name: "onedrive_share",
1286
+ description: "Create a sharing link for a file or folder.",
1287
+ category: "utility",
1288
+ parameters: {
1289
+ type: "object",
1290
+ properties: {
1291
+ itemId: { type: "string", description: "Item ID to share" },
1292
+ type: { type: "string", description: "view or edit (default: view)" },
1293
+ scope: { type: "string", description: "anonymous (anyone) or organization (default: organization)" },
1294
+ expirationDateTime: { type: "string", description: "Link expiry (ISO 8601). Only for anonymous links." },
1295
+ password: { type: "string", description: "Password-protect the link (only for anonymous links)." }
1296
+ },
1297
+ required: ["itemId"]
1298
+ },
1299
+ async execute(_id, params) {
1300
+ try {
1301
+ const token = await tp.getAccessToken();
1302
+ const body = { type: params.type || "view", scope: params.scope || "organization" };
1303
+ if (params.expirationDateTime) body.expirationDateTime = params.expirationDateTime;
1304
+ if (params.password) body.password = params.password;
1305
+ const link = await graph(token, `/me/drive/items/${params.itemId}/createLink`, {
1306
+ method: "POST",
1307
+ body
1308
+ });
1309
+ return jsonResult({ url: link.link?.webUrl, type: link.link?.type, scope: link.link?.scope, expiration: link.link?.expirationDateTime });
1310
+ } catch (e) {
1311
+ return errorResult(e.message);
1312
+ }
1313
+ }
1314
+ },
1315
+ {
1316
+ name: "onedrive_move",
1317
+ description: "Move or rename a file/folder in OneDrive.",
1318
+ category: "utility",
1319
+ parameters: {
1320
+ type: "object",
1321
+ properties: {
1322
+ itemId: { type: "string", description: "Item ID to move/rename" },
1323
+ newName: { type: "string", description: "New name (optional, for rename)" },
1324
+ destinationFolderId: { type: "string", description: "Destination folder ID (optional, for move)" },
1325
+ destinationPath: { type: "string", description: 'Destination folder path (alternative to ID, e.g., "/Documents/Archive")' }
1326
+ },
1327
+ required: ["itemId"]
1328
+ },
1329
+ async execute(_id, params) {
1330
+ try {
1331
+ const token = await tp.getAccessToken();
1332
+ const body = {};
1333
+ if (params.newName) body.name = params.newName;
1334
+ if (params.destinationFolderId) {
1335
+ body.parentReference = { id: params.destinationFolderId };
1336
+ } else if (params.destinationPath) {
1337
+ const dest = await graph(token, `/me/drive/root:${params.destinationPath}`, {
1338
+ query: { "$select": "id" }
1339
+ });
1340
+ body.parentReference = { id: dest.id };
1341
+ }
1342
+ if (!body.name && !body.parentReference) {
1343
+ return errorResult("Provide newName (rename), destinationFolderId or destinationPath (move), or both.");
1344
+ }
1345
+ const updated = await graph(token, `/me/drive/items/${params.itemId}`, {
1346
+ method: "PATCH",
1347
+ body
1348
+ });
1349
+ return jsonResult({ id: updated.id, name: updated.name, webUrl: updated.webUrl, path: updated.parentReference?.path });
1350
+ } catch (e) {
1351
+ return errorResult(e.message);
1352
+ }
1353
+ }
1354
+ },
1355
+ {
1356
+ name: "onedrive_copy",
1357
+ description: "Copy a file or folder to a new location in OneDrive. Returns a monitor URL for large copies.",
1358
+ category: "utility",
1359
+ parameters: {
1360
+ type: "object",
1361
+ properties: {
1362
+ itemId: { type: "string", description: "Item ID to copy" },
1363
+ destinationFolderId: { type: "string", description: "Destination folder ID" },
1364
+ destinationPath: { type: "string", description: "Destination folder path (alternative to ID)" },
1365
+ newName: { type: "string", description: "New name for the copy (optional)" }
1366
+ },
1367
+ required: ["itemId"]
1368
+ },
1369
+ async execute(_id, params) {
1370
+ try {
1371
+ const token = await tp.getAccessToken();
1372
+ const body = {};
1373
+ if (params.newName) body.name = params.newName;
1374
+ if (params.destinationFolderId) {
1375
+ body.parentReference = { driveId: "me", id: params.destinationFolderId };
1376
+ } else if (params.destinationPath) {
1377
+ const dest = await graph(token, `/me/drive/root:${params.destinationPath}`, { query: { "$select": "id,parentReference" } });
1378
+ body.parentReference = { driveId: dest.parentReference?.driveId || "me", id: dest.id };
1379
+ } else {
1380
+ return errorResult("Provide destinationFolderId or destinationPath.");
1381
+ }
1382
+ const res = await fetch(`https://graph.microsoft.com/v1.0/me/drive/items/${params.itemId}/copy`, {
1383
+ method: "POST",
1384
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
1385
+ body: JSON.stringify(body)
1386
+ });
1387
+ if (res.status === 202) {
1388
+ return jsonResult({ status: "copying", monitorUrl: res.headers.get("Location"), message: "Copy started. Large files may take time." });
1389
+ }
1390
+ if (!res.ok) throw new Error(`Copy failed: ${res.status} ${await res.text()}`);
1391
+ const data = await res.json();
1392
+ return jsonResult({ id: data.id, name: data.name, webUrl: data.webUrl });
1393
+ } catch (e) {
1394
+ return errorResult(e.message);
1395
+ }
1396
+ }
1397
+ },
1398
+ {
1399
+ name: "onedrive_versions",
1400
+ description: "List version history of a file in OneDrive.",
1401
+ category: "utility",
1402
+ parameters: {
1403
+ type: "object",
1404
+ properties: {
1405
+ itemId: { type: "string", description: "File item ID" },
1406
+ path: { type: "string", description: "File path (alternative to itemId)" }
1407
+ },
1408
+ required: []
1409
+ },
1410
+ async execute(_id, params) {
1411
+ try {
1412
+ const token = await tp.getAccessToken();
1413
+ const base = params.itemId ? `/me/drive/items/${params.itemId}` : `/me/drive/root:${params.path}:`;
1414
+ const data = await graph(token, `${base}/versions`);
1415
+ const versions = (data.value || []).map((v) => ({
1416
+ id: v.id,
1417
+ size: v.size,
1418
+ modified: v.lastModifiedDateTime,
1419
+ modifiedBy: v.lastModifiedBy?.user?.displayName || v.lastModifiedBy?.user?.email
1420
+ }));
1421
+ return jsonResult({ versions, count: versions.length });
1422
+ } catch (e) {
1423
+ return errorResult(e.message);
1424
+ }
1425
+ }
1426
+ },
1427
+ {
1428
+ name: "onedrive_recent",
1429
+ description: "List recently accessed files in OneDrive.",
1430
+ category: "utility",
1431
+ parameters: {
1432
+ type: "object",
1433
+ properties: {
1434
+ maxResults: { type: "number", description: "Max items (default: 20)" }
1435
+ },
1436
+ required: []
1437
+ },
1438
+ async execute(_id, params) {
1439
+ try {
1440
+ const token = await tp.getAccessToken();
1441
+ const data = await graph(token, "/me/drive/recent", {
1442
+ query: { "$top": String(params.maxResults || 20), "$select": "id,name,size,webUrl,lastModifiedDateTime,file,remoteItem" }
1443
+ });
1444
+ const items = (data.value || []).map((i) => {
1445
+ const item = i.remoteItem || i;
1446
+ return {
1447
+ id: item.id,
1448
+ name: item.name || i.name,
1449
+ size: item.size,
1450
+ mimeType: item.file?.mimeType,
1451
+ modified: item.lastModifiedDateTime || i.lastModifiedDateTime,
1452
+ webUrl: item.webUrl || i.webUrl
1453
+ };
1454
+ });
1455
+ return jsonResult({ items, count: items.length });
1456
+ } catch (e) {
1457
+ return errorResult(e.message);
1458
+ }
1459
+ }
1460
+ },
1461
+ {
1462
+ name: "onedrive_permissions",
1463
+ description: "List or manage sharing permissions on a file/folder.",
1464
+ category: "utility",
1465
+ parameters: {
1466
+ type: "object",
1467
+ properties: {
1468
+ itemId: { type: "string", description: "Item ID" },
1469
+ action: { type: "string", description: "list (default), revoke, or invite" },
1470
+ permissionId: { type: "string", description: "Permission ID to revoke (for action=revoke)" },
1471
+ email: { type: "string", description: "Email to invite (for action=invite)" },
1472
+ role: { type: "string", description: "read or write (for action=invite, default: read)" },
1473
+ message: { type: "string", description: "Optional message for invite" }
1474
+ },
1475
+ required: ["itemId"]
1476
+ },
1477
+ async execute(_id, params) {
1478
+ try {
1479
+ const token = await tp.getAccessToken();
1480
+ const action = params.action || "list";
1481
+ if (action === "revoke") {
1482
+ if (!params.permissionId) return errorResult("permissionId required for revoke");
1483
+ await graph(token, `/me/drive/items/${params.itemId}/permissions/${params.permissionId}`, { method: "DELETE" });
1484
+ return jsonResult({ revoked: true, permissionId: params.permissionId });
1485
+ }
1486
+ if (action === "invite") {
1487
+ if (!params.email) return errorResult("email required for invite");
1488
+ const invite = await graph(token, `/me/drive/items/${params.itemId}/invite`, {
1489
+ method: "POST",
1490
+ body: {
1491
+ recipients: [{ email: params.email }],
1492
+ roles: [params.role || "read"],
1493
+ requireSignIn: true,
1494
+ sendInvitation: true,
1495
+ message: params.message || ""
1496
+ }
1497
+ });
1498
+ return jsonResult({ invited: true, email: params.email, permissions: invite.value });
1499
+ }
1500
+ const data = await graph(token, `/me/drive/items/${params.itemId}/permissions`);
1501
+ const perms = (data.value || []).map((p) => ({
1502
+ id: p.id,
1503
+ roles: p.roles,
1504
+ grantedTo: p.grantedToV2?.user?.displayName || p.grantedTo?.user?.displayName,
1505
+ email: p.grantedToV2?.user?.email || p.invitation?.email,
1506
+ link: p.link ? { type: p.link.type, scope: p.link.scope, webUrl: p.link.webUrl } : void 0
1507
+ }));
1508
+ return jsonResult({ permissions: perms, count: perms.length });
1509
+ } catch (e) {
1510
+ return errorResult(e.message);
1511
+ }
1512
+ }
1513
+ }
1514
+ ];
1515
+ }
1516
+
1517
+ // src/agent-tools/tools/microsoft/teams.ts
1518
+ function mapMessage2(m) {
1519
+ return {
1520
+ id: m.id,
1521
+ from: m.from?.user?.displayName || m.from?.application?.displayName,
1522
+ fromEmail: m.from?.user?.email,
1523
+ fromUserId: m.from?.user?.id,
1524
+ body: m.body?.content,
1525
+ bodyType: m.body?.contentType,
1526
+ date: m.createdDateTime,
1527
+ lastModified: m.lastModifiedDateTime,
1528
+ importance: m.importance,
1529
+ subject: m.subject,
1530
+ attachments: m.attachments?.map((a) => ({
1531
+ id: a.id,
1532
+ name: a.name,
1533
+ contentType: a.contentType,
1534
+ contentUrl: a.contentUrl
1535
+ })),
1536
+ mentions: m.mentions?.map((mt) => ({ id: mt.id, text: mt.mentionText, userId: mt.mentioned?.user?.id })),
1537
+ reactions: m.reactions?.map((r) => ({ type: r.reactionType, user: r.user?.user?.displayName })),
1538
+ replyCount: m.replies?.length
1539
+ };
1540
+ }
1541
+ function createTeamsTools(config, _options) {
1542
+ const tp = config.tokenProvider;
1543
+ return [
1544
+ {
1545
+ name: "teams_list_teams",
1546
+ description: "List all Teams the agent is a member of, with descriptions and visibility.",
1547
+ category: "utility",
1548
+ parameters: { type: "object", properties: {}, required: [] },
1549
+ async execute(_id) {
1550
+ try {
1551
+ const token = await tp.getAccessToken();
1552
+ const data = await graph(token, "/me/joinedTeams", {
1553
+ query: { "$select": "id,displayName,description,visibility,isArchived" }
1554
+ });
1555
+ const teams = (data.value || []).map((t) => ({
1556
+ id: t.id,
1557
+ name: t.displayName,
1558
+ description: t.description,
1559
+ visibility: t.visibility,
1560
+ isArchived: t.isArchived
1561
+ }));
1562
+ return jsonResult({ teams, count: teams.length });
1563
+ } catch (e) {
1564
+ return errorResult(e.message);
1565
+ }
1566
+ }
1567
+ },
1568
+ {
1569
+ name: "teams_list_channels",
1570
+ description: "List channels in a Team with membership type and description.",
1571
+ category: "utility",
1572
+ parameters: {
1573
+ type: "object",
1574
+ properties: {
1575
+ teamId: { type: "string", description: "Team ID" },
1576
+ includePrivate: { type: "boolean", description: "Include private/shared channels (default: true)" }
1577
+ },
1578
+ required: ["teamId"]
1579
+ },
1580
+ async execute(_id, params) {
1581
+ try {
1582
+ const token = await tp.getAccessToken();
1583
+ const data = await graph(token, `/teams/${params.teamId}/channels`, {
1584
+ query: { "$select": "id,displayName,description,membershipType,webUrl,createdDateTime" }
1585
+ });
1586
+ const channels = (data.value || []).map((c) => ({
1587
+ id: c.id,
1588
+ name: c.displayName,
1589
+ description: c.description,
1590
+ membershipType: c.membershipType,
1591
+ webUrl: c.webUrl,
1592
+ created: c.createdDateTime
1593
+ }));
1594
+ return jsonResult({ channels, count: channels.length });
1595
+ } catch (e) {
1596
+ return errorResult(e.message);
1597
+ }
1598
+ }
1599
+ },
1600
+ {
1601
+ name: "teams_create_channel",
1602
+ description: "Create a new channel in a Team.",
1603
+ category: "utility",
1604
+ parameters: {
1605
+ type: "object",
1606
+ properties: {
1607
+ teamId: { type: "string", description: "Team ID" },
1608
+ name: { type: "string", description: "Channel display name" },
1609
+ description: { type: "string", description: "Channel description" },
1610
+ membershipType: { type: "string", description: "standard or private (default: standard)" }
1611
+ },
1612
+ required: ["teamId", "name"]
1613
+ },
1614
+ async execute(_id, params) {
1615
+ try {
1616
+ const token = await tp.getAccessToken();
1617
+ const channel = await graph(token, `/teams/${params.teamId}/channels`, {
1618
+ method: "POST",
1619
+ body: {
1620
+ displayName: params.name,
1621
+ description: params.description || "",
1622
+ membershipType: params.membershipType || "standard"
1623
+ }
1624
+ });
1625
+ return jsonResult({ id: channel.id, name: channel.displayName, created: true });
1626
+ } catch (e) {
1627
+ return errorResult(e.message);
1628
+ }
1629
+ }
1630
+ },
1631
+ {
1632
+ name: "teams_send_channel_message",
1633
+ description: "Send a message to a Teams channel. Supports HTML, @mentions, and importance.",
1634
+ category: "utility",
1635
+ parameters: {
1636
+ type: "object",
1637
+ properties: {
1638
+ teamId: { type: "string", description: "Team ID" },
1639
+ channelId: { type: "string", description: "Channel ID" },
1640
+ message: { type: "string", description: "Message content (supports HTML)" },
1641
+ isHtml: { type: "boolean", description: "Whether message is HTML (default: false)" },
1642
+ importance: { type: "string", description: "normal, high, or urgent" },
1643
+ subject: { type: "string", description: "Message subject (creates a thread header)" }
1644
+ },
1645
+ required: ["teamId", "channelId", "message"]
1646
+ },
1647
+ async execute(_id, params) {
1648
+ try {
1649
+ const token = await tp.getAccessToken();
1650
+ const body = {
1651
+ body: { contentType: params.isHtml ? "html" : "text", content: params.message }
1652
+ };
1653
+ if (params.importance) body.importance = params.importance;
1654
+ if (params.subject) body.subject = params.subject;
1655
+ const msg = await graph(token, `/teams/${params.teamId}/channels/${params.channelId}/messages`, {
1656
+ method: "POST",
1657
+ body
1658
+ });
1659
+ return jsonResult({ id: msg.id, sent: true });
1660
+ } catch (e) {
1661
+ return errorResult(e.message);
1662
+ }
1663
+ }
1664
+ },
1665
+ {
1666
+ name: "teams_reply_to_message",
1667
+ description: "Reply to a specific message in a Teams channel (threaded conversation).",
1668
+ category: "utility",
1669
+ parameters: {
1670
+ type: "object",
1671
+ properties: {
1672
+ teamId: { type: "string", description: "Team ID" },
1673
+ channelId: { type: "string", description: "Channel ID" },
1674
+ messageId: { type: "string", description: "Parent message ID to reply to" },
1675
+ message: { type: "string", description: "Reply content" },
1676
+ isHtml: { type: "boolean", description: "Whether reply is HTML" }
1677
+ },
1678
+ required: ["teamId", "channelId", "messageId", "message"]
1679
+ },
1680
+ async execute(_id, params) {
1681
+ try {
1682
+ const token = await tp.getAccessToken();
1683
+ const reply = await graph(token, `/teams/${params.teamId}/channels/${params.channelId}/messages/${params.messageId}/replies`, {
1684
+ method: "POST",
1685
+ body: { body: { contentType: params.isHtml ? "html" : "text", content: params.message } }
1686
+ });
1687
+ return jsonResult({ id: reply.id, sent: true, parentId: params.messageId });
1688
+ } catch (e) {
1689
+ return errorResult(e.message);
1690
+ }
1691
+ }
1692
+ },
1693
+ {
1694
+ name: "teams_read_channel_messages",
1695
+ description: "Read recent messages from a Teams channel, including replies and reactions.",
1696
+ category: "utility",
1697
+ parameters: {
1698
+ type: "object",
1699
+ properties: {
1700
+ teamId: { type: "string", description: "Team ID" },
1701
+ channelId: { type: "string", description: "Channel ID" },
1702
+ maxResults: { type: "number", description: "Max messages (default: 20)" },
1703
+ includeReplies: { type: "boolean", description: "Include thread replies (default: false \u2014 heavier API call)" }
1704
+ },
1705
+ required: ["teamId", "channelId"]
1706
+ },
1707
+ async execute(_id, params) {
1708
+ try {
1709
+ const token = await tp.getAccessToken();
1710
+ const query = {
1711
+ "$top": String(params.maxResults || 20)
1712
+ };
1713
+ if (params.includeReplies) query["$expand"] = "replies";
1714
+ const data = await graph(token, `/teams/${params.teamId}/channels/${params.channelId}/messages`, { query });
1715
+ const messages = (data.value || []).map((m) => {
1716
+ const msg = mapMessage2(m);
1717
+ if (params.includeReplies && m.replies?.length) {
1718
+ msg.replies = m.replies.map((r) => mapMessage2(r));
1719
+ }
1720
+ return msg;
1721
+ });
1722
+ return jsonResult({ messages, count: messages.length });
1723
+ } catch (e) {
1724
+ return errorResult(e.message);
1725
+ }
1726
+ }
1727
+ },
1728
+ {
1729
+ name: "teams_list_chats",
1730
+ description: "List the agent's 1:1 and group chats in Teams.",
1731
+ category: "utility",
1732
+ parameters: {
1733
+ type: "object",
1734
+ properties: {
1735
+ maxResults: { type: "number", description: "Max chats to return (default: 20)" },
1736
+ filter: { type: "string", description: 'Filter: "oneOnOne", "group", or "meeting" (default: all)' }
1737
+ },
1738
+ required: []
1739
+ },
1740
+ async execute(_id, params) {
1741
+ try {
1742
+ const token = await tp.getAccessToken();
1743
+ const query = {
1744
+ "$top": String(params.maxResults || 20),
1745
+ "$select": "id,topic,chatType,createdDateTime,lastUpdatedDateTime",
1746
+ "$orderby": "lastUpdatedDateTime desc"
1747
+ };
1748
+ if (params.filter) query["$filter"] = `chatType eq '${params.filter}'`;
1749
+ const data = await graph(token, "/me/chats", { query });
1750
+ const chats = (data.value || []).map((c) => ({
1751
+ id: c.id,
1752
+ topic: c.topic,
1753
+ type: c.chatType,
1754
+ created: c.createdDateTime,
1755
+ lastUpdated: c.lastUpdatedDateTime
1756
+ }));
1757
+ return jsonResult({ chats, count: chats.length });
1758
+ } catch (e) {
1759
+ return errorResult(e.message);
1760
+ }
1761
+ }
1762
+ },
1763
+ {
1764
+ name: "teams_send_chat_message",
1765
+ description: "Send a message in a Teams 1:1 or group chat.",
1766
+ category: "utility",
1767
+ parameters: {
1768
+ type: "object",
1769
+ properties: {
1770
+ chatId: { type: "string", description: "Chat ID" },
1771
+ message: { type: "string", description: "Message content" },
1772
+ isHtml: { type: "boolean", description: "Whether message is HTML (default: false)" }
1773
+ },
1774
+ required: ["chatId", "message"]
1775
+ },
1776
+ async execute(_id, params) {
1777
+ try {
1778
+ const token = await tp.getAccessToken();
1779
+ const msg = await graph(token, `/chats/${params.chatId}/messages`, {
1780
+ method: "POST",
1781
+ body: { body: { contentType: params.isHtml ? "html" : "text", content: params.message } }
1782
+ });
1783
+ return jsonResult({ id: msg.id, sent: true });
1784
+ } catch (e) {
1785
+ return errorResult(e.message);
1786
+ }
1787
+ }
1788
+ },
1789
+ {
1790
+ name: "teams_read_chat_messages",
1791
+ description: "Read recent messages from a Teams chat.",
1792
+ category: "utility",
1793
+ parameters: {
1794
+ type: "object",
1795
+ properties: {
1796
+ chatId: { type: "string", description: "Chat ID" },
1797
+ maxResults: { type: "number", description: "Max messages (default: 20)" }
1798
+ },
1799
+ required: ["chatId"]
1800
+ },
1801
+ async execute(_id, params) {
1802
+ try {
1803
+ const token = await tp.getAccessToken();
1804
+ const data = await graph(token, `/chats/${params.chatId}/messages`, {
1805
+ query: { "$top": String(params.maxResults || 20), "$orderby": "createdDateTime desc" }
1806
+ });
1807
+ const messages = (data.value || []).map((m) => mapMessage2(m));
1808
+ return jsonResult({ messages, count: messages.length });
1809
+ } catch (e) {
1810
+ return errorResult(e.message);
1811
+ }
1812
+ }
1813
+ },
1814
+ {
1815
+ name: "teams_list_members",
1816
+ description: "List members of a team or channel.",
1817
+ category: "utility",
1818
+ parameters: {
1819
+ type: "object",
1820
+ properties: {
1821
+ teamId: { type: "string", description: "Team ID" },
1822
+ channelId: { type: "string", description: "Channel ID (omit for team members)" }
1823
+ },
1824
+ required: ["teamId"]
1825
+ },
1826
+ async execute(_id, params) {
1827
+ try {
1828
+ const token = await tp.getAccessToken();
1829
+ const path = params.channelId ? `/teams/${params.teamId}/channels/${params.channelId}/members` : `/teams/${params.teamId}/members`;
1830
+ const data = await graph(token, path);
1831
+ const members = (data.value || []).map((m) => ({
1832
+ id: m.id,
1833
+ userId: m.userId,
1834
+ name: m.displayName,
1835
+ email: m.email,
1836
+ roles: m.roles
1837
+ // ['owner'] or []
1838
+ }));
1839
+ return jsonResult({ members, count: members.length });
1840
+ } catch (e) {
1841
+ return errorResult(e.message);
1842
+ }
1843
+ }
1844
+ },
1845
+ {
1846
+ name: "teams_add_member",
1847
+ description: "Add a member to a team.",
1848
+ category: "utility",
1849
+ parameters: {
1850
+ type: "object",
1851
+ properties: {
1852
+ teamId: { type: "string", description: "Team ID" },
1853
+ userId: { type: "string", description: "User ID or email to add" },
1854
+ role: { type: "string", description: "member or owner (default: member)" }
1855
+ },
1856
+ required: ["teamId", "userId"]
1857
+ },
1858
+ async execute(_id, params) {
1859
+ try {
1860
+ const token = await tp.getAccessToken();
1861
+ const body = {
1862
+ "@odata.type": "#microsoft.graph.aadUserConversationMember",
1863
+ "user@odata.bind": `https://graph.microsoft.com/v1.0/users('${params.userId}')`,
1864
+ roles: params.role === "owner" ? ["owner"] : []
1865
+ };
1866
+ await graph(token, `/teams/${params.teamId}/members`, { method: "POST", body });
1867
+ return jsonResult({ added: true, userId: params.userId, teamId: params.teamId });
1868
+ } catch (e) {
1869
+ return errorResult(e.message);
1870
+ }
1871
+ }
1872
+ },
1873
+ {
1874
+ name: "teams_share_file",
1875
+ description: "Share a file in a Teams channel by uploading it to the channel's SharePoint folder.",
1876
+ category: "utility",
1877
+ parameters: {
1878
+ type: "object",
1879
+ properties: {
1880
+ teamId: { type: "string", description: "Team ID" },
1881
+ channelId: { type: "string", description: "Channel ID" },
1882
+ fileName: { type: "string", description: 'File name (e.g., "report.pdf")' },
1883
+ content: { type: "string", description: "File content (text or base64 for binary)" },
1884
+ message: { type: "string", description: "Optional message to post with the file" }
1885
+ },
1886
+ required: ["teamId", "channelId", "fileName", "content"]
1887
+ },
1888
+ async execute(_id, params) {
1889
+ try {
1890
+ const token = await tp.getAccessToken();
1891
+ const folder = await graph(token, `/teams/${params.teamId}/channels/${params.channelId}/filesFolder`);
1892
+ const driveId = folder.parentReference?.driveId;
1893
+ const folderId = folder.id;
1894
+ if (!driveId) throw new Error("Could not resolve channel files folder");
1895
+ const uploadRes = await fetch(`https://graph.microsoft.com/v1.0/drives/${driveId}/items/${folderId}:/${encodeURIComponent(params.fileName)}:/content`, {
1896
+ method: "PUT",
1897
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/octet-stream" },
1898
+ body: params.content
1899
+ });
1900
+ if (!uploadRes.ok) throw new Error(`Upload failed: ${uploadRes.status}`);
1901
+ const file = await uploadRes.json();
1902
+ if (params.message) {
1903
+ await graph(token, `/teams/${params.teamId}/channels/${params.channelId}/messages`, {
1904
+ method: "POST",
1905
+ body: {
1906
+ body: { contentType: "html", content: `${params.message}<br/><a href="${file.webUrl}">${params.fileName}</a>` }
1907
+ }
1908
+ });
1909
+ }
1910
+ return jsonResult({ fileId: file.id, name: file.name, webUrl: file.webUrl, uploaded: true });
1911
+ } catch (e) {
1912
+ return errorResult(e.message);
1913
+ }
1914
+ }
1915
+ },
1916
+ {
1917
+ name: "teams_presence",
1918
+ description: "Get presence/availability status for users (Available, Busy, DoNotDisturb, Away, Offline).",
1919
+ category: "utility",
1920
+ parameters: {
1921
+ type: "object",
1922
+ properties: {
1923
+ userIds: { type: "string", description: 'User IDs, comma-separated (use "me" for self)' }
1924
+ },
1925
+ required: ["userIds"]
1926
+ },
1927
+ async execute(_id, params) {
1928
+ try {
1929
+ const token = await tp.getAccessToken();
1930
+ if (params.userIds === "me") {
1931
+ const p = await graph(token, "/me/presence");
1932
+ return jsonResult({ presence: [{ availability: p.availability, activity: p.activity, statusMessage: p.statusMessage?.message?.content }] });
1933
+ }
1934
+ const ids = params.userIds.split(",").map((s) => s.trim());
1935
+ const data = await graph(token, "/communications/getPresencesByUserId", {
1936
+ method: "POST",
1937
+ body: { ids }
1938
+ });
1939
+ const presence = (data.value || []).map((p) => ({
1940
+ userId: p.id,
1941
+ availability: p.availability,
1942
+ activity: p.activity
1943
+ }));
1944
+ return jsonResult({ presence });
1945
+ } catch (e) {
1946
+ return errorResult(e.message);
1947
+ }
1948
+ }
1949
+ },
1950
+ {
1951
+ name: "teams_set_status",
1952
+ description: "Set the agent's presence status message in Teams.",
1953
+ category: "utility",
1954
+ parameters: {
1955
+ type: "object",
1956
+ properties: {
1957
+ message: { type: "string", description: "Status message text" },
1958
+ expiry: { type: "string", description: 'Expiry duration (ISO 8601 duration, e.g., "PT1H" for 1 hour, "P1D" for 1 day)' }
1959
+ },
1960
+ required: ["message"]
1961
+ },
1962
+ async execute(_id, params) {
1963
+ try {
1964
+ const token = await tp.getAccessToken();
1965
+ const body = {
1966
+ statusMessage: {
1967
+ message: { content: params.message, contentType: "text" }
1968
+ }
1969
+ };
1970
+ if (params.expiry) {
1971
+ body.statusMessage.expiryDateTime = {
1972
+ dateTime: new Date(Date.now() + parseDuration(params.expiry)).toISOString(),
1973
+ timeZone: "UTC"
1974
+ };
1975
+ }
1976
+ await graph(token, "/me/presence/setStatusMessage", { method: "POST", body });
1977
+ return jsonResult({ statusSet: true, message: params.message });
1978
+ } catch (e) {
1979
+ return errorResult(e.message);
1980
+ }
1981
+ }
1982
+ }
1983
+ ];
1984
+ }
1985
+ function parseDuration(iso) {
1986
+ const match = iso.match(/P(?:(\d+)D)?T?(?:(\d+)H)?(?:(\d+)M)?/);
1987
+ if (!match) return 36e5;
1988
+ const days = parseInt(match[1] || "0") * 864e5;
1989
+ const hours = parseInt(match[2] || "0") * 36e5;
1990
+ const mins = parseInt(match[3] || "0") * 6e4;
1991
+ return days + hours + mins;
1992
+ }
1993
+
1994
+ // src/agent-tools/tools/microsoft/todo.ts
1995
+ function createTodoTools(config, _options) {
1996
+ const tp = config.tokenProvider;
1997
+ return [
1998
+ {
1999
+ name: "todo_list_lists",
2000
+ description: "List all To Do task lists.",
2001
+ category: "utility",
2002
+ parameters: { type: "object", properties: {}, required: [] },
2003
+ async execute(_id) {
2004
+ try {
2005
+ const token = await tp.getAccessToken();
2006
+ const data = await graph(token, "/me/todo/lists");
2007
+ const lists = (data.value || []).map((l) => ({
2008
+ id: l.id,
2009
+ name: l.displayName,
2010
+ isOwner: l.isOwner,
2011
+ wellknownName: l.wellknownListName
2012
+ }));
2013
+ return jsonResult({ lists, count: lists.length });
2014
+ } catch (e) {
2015
+ return errorResult(e.message);
2016
+ }
2017
+ }
2018
+ },
2019
+ {
2020
+ name: "todo_list_tasks",
2021
+ description: "List tasks in a To Do list.",
2022
+ category: "utility",
2023
+ parameters: {
2024
+ type: "object",
2025
+ properties: {
2026
+ listId: { type: "string", description: "Task list ID" },
2027
+ includeCompleted: { type: "boolean", description: "Include completed tasks (default: false)" },
2028
+ maxResults: { type: "number", description: "Max tasks (default: 50)" }
2029
+ },
2030
+ required: ["listId"]
2031
+ },
2032
+ async execute(_id, params) {
2033
+ try {
2034
+ const token = await tp.getAccessToken();
2035
+ const query = {
2036
+ "$top": String(params.maxResults || 50),
2037
+ "$orderby": "importance desc,createdDateTime desc",
2038
+ "$select": "id,title,body,status,importance,createdDateTime,lastModifiedDateTime,dueDateTime,completedDateTime,isReminderOn,reminderDateTime"
2039
+ };
2040
+ if (!params.includeCompleted) query["$filter"] = "status ne 'completed'";
2041
+ const data = await graph(token, `/me/todo/lists/${params.listId}/tasks`, { query });
2042
+ const tasks = (data.value || []).map((t) => ({
2043
+ id: t.id,
2044
+ title: t.title,
2045
+ body: t.body?.content,
2046
+ bodyType: t.body?.contentType,
2047
+ status: t.status,
2048
+ importance: t.importance,
2049
+ created: t.createdDateTime,
2050
+ modified: t.lastModifiedDateTime,
2051
+ dueDate: t.dueDateTime?.dateTime,
2052
+ dueTimeZone: t.dueDateTime?.timeZone,
2053
+ completedDate: t.completedDateTime?.dateTime,
2054
+ hasReminder: t.isReminderOn
2055
+ }));
2056
+ return jsonResult({ tasks, count: tasks.length });
2057
+ } catch (e) {
2058
+ return errorResult(e.message);
2059
+ }
2060
+ }
2061
+ },
2062
+ {
2063
+ name: "todo_create_task",
2064
+ description: "Create a new task in a To Do list.",
2065
+ category: "utility",
2066
+ parameters: {
2067
+ type: "object",
2068
+ properties: {
2069
+ listId: { type: "string", description: "Task list ID" },
2070
+ title: { type: "string", description: "Task title" },
2071
+ body: { type: "string", description: "Task notes/description" },
2072
+ dueDate: { type: "string", description: 'Due date (ISO 8601 date, e.g., "2026-03-15")' },
2073
+ importance: { type: "string", description: "low, normal, or high (default: normal)" },
2074
+ reminderDateTime: { type: "string", description: "Reminder date/time (ISO 8601)" }
2075
+ },
2076
+ required: ["listId", "title"]
2077
+ },
2078
+ async execute(_id, params) {
2079
+ try {
2080
+ const token = await tp.getAccessToken();
2081
+ const task = { title: params.title };
2082
+ if (params.body) task.body = { contentType: "text", content: params.body };
2083
+ if (params.dueDate) task.dueDateTime = { dateTime: params.dueDate + "T00:00:00", timeZone: "UTC" };
2084
+ if (params.importance) task.importance = params.importance;
2085
+ if (params.reminderDateTime) {
2086
+ task.isReminderOn = true;
2087
+ task.reminderDateTime = { dateTime: params.reminderDateTime, timeZone: "UTC" };
2088
+ }
2089
+ const created = await graph(token, `/me/todo/lists/${params.listId}/tasks`, { method: "POST", body: task });
2090
+ return jsonResult({ id: created.id, title: created.title, status: created.status });
2091
+ } catch (e) {
2092
+ return errorResult(e.message);
2093
+ }
2094
+ }
2095
+ },
2096
+ {
2097
+ name: "todo_update_task",
2098
+ description: "Update an existing To Do task (title, status, due date, importance).",
2099
+ category: "utility",
2100
+ parameters: {
2101
+ type: "object",
2102
+ properties: {
2103
+ listId: { type: "string", description: "Task list ID" },
2104
+ taskId: { type: "string", description: "Task ID" },
2105
+ title: { type: "string", description: "New title" },
2106
+ status: { type: "string", description: "notStarted, inProgress, completed, waitingOnOthers, deferred" },
2107
+ importance: { type: "string", description: "low, normal, or high" },
2108
+ dueDate: { type: "string", description: "New due date (ISO 8601 date)" },
2109
+ body: { type: "string", description: "New task notes" }
2110
+ },
2111
+ required: ["listId", "taskId"]
2112
+ },
2113
+ async execute(_id, params) {
2114
+ try {
2115
+ const token = await tp.getAccessToken();
2116
+ const update = {};
2117
+ if (params.title) update.title = params.title;
2118
+ if (params.status) update.status = params.status;
2119
+ if (params.importance) update.importance = params.importance;
2120
+ if (params.dueDate) update.dueDateTime = { dateTime: params.dueDate + "T00:00:00", timeZone: "UTC" };
2121
+ if (params.body) update.body = { contentType: "text", content: params.body };
2122
+ const updated = await graph(token, `/me/todo/lists/${params.listId}/tasks/${params.taskId}`, {
2123
+ method: "PATCH",
2124
+ body: update
2125
+ });
2126
+ return jsonResult({ id: updated.id, title: updated.title, status: updated.status, updated: true });
2127
+ } catch (e) {
2128
+ return errorResult(e.message);
2129
+ }
2130
+ }
2131
+ },
2132
+ {
2133
+ name: "todo_delete_task",
2134
+ description: "Delete a To Do task.",
2135
+ category: "utility",
2136
+ parameters: {
2137
+ type: "object",
2138
+ properties: {
2139
+ listId: { type: "string", description: "Task list ID" },
2140
+ taskId: { type: "string", description: "Task ID to delete" }
2141
+ },
2142
+ required: ["listId", "taskId"]
2143
+ },
2144
+ async execute(_id, params) {
2145
+ try {
2146
+ const token = await tp.getAccessToken();
2147
+ await graph(token, `/me/todo/lists/${params.listId}/tasks/${params.taskId}`, { method: "DELETE" });
2148
+ return jsonResult({ deleted: true, taskId: params.taskId });
2149
+ } catch (e) {
2150
+ return errorResult(e.message);
2151
+ }
2152
+ }
2153
+ },
2154
+ {
2155
+ name: "todo_create_list",
2156
+ description: "Create a new To Do task list.",
2157
+ category: "utility",
2158
+ parameters: {
2159
+ type: "object",
2160
+ properties: {
2161
+ name: { type: "string", description: "List name" }
2162
+ },
2163
+ required: ["name"]
2164
+ },
2165
+ async execute(_id, params) {
2166
+ try {
2167
+ const token = await tp.getAccessToken();
2168
+ const list = await graph(token, "/me/todo/lists", {
2169
+ method: "POST",
2170
+ body: { displayName: params.name }
2171
+ });
2172
+ return jsonResult({ id: list.id, name: list.displayName });
2173
+ } catch (e) {
2174
+ return errorResult(e.message);
2175
+ }
2176
+ }
2177
+ }
2178
+ ];
2179
+ }
2180
+
2181
+ // src/agent-tools/tools/microsoft/contacts.ts
2182
+ function createOutlookContactsTools(config, _options) {
2183
+ const tp = config.tokenProvider;
2184
+ return [
2185
+ {
2186
+ name: "outlook_contacts_list",
2187
+ description: "List contacts from the Outlook address book.",
2188
+ category: "utility",
2189
+ parameters: {
2190
+ type: "object",
2191
+ properties: {
2192
+ maxResults: { type: "number", description: "Max contacts (default: 50)" },
2193
+ search: { type: "string", description: "Search by name or email" }
2194
+ },
2195
+ required: []
2196
+ },
2197
+ async execute(_id, params) {
2198
+ try {
2199
+ const token = await tp.getAccessToken();
2200
+ const query = {
2201
+ "$top": String(params.maxResults || 50),
2202
+ "$select": "id,displayName,givenName,surname,emailAddresses,mobilePhone,businessPhones,companyName,jobTitle",
2203
+ "$orderby": "displayName"
2204
+ };
2205
+ if (params.search) query["$filter"] = `startswith(displayName,'${params.search}') or startswith(givenName,'${params.search}') or startswith(surname,'${params.search}')`;
2206
+ const data = await graph(token, "/me/contacts", { query });
2207
+ const contacts = (data.value || []).map((c) => ({
2208
+ id: c.id,
2209
+ name: c.displayName,
2210
+ firstName: c.givenName,
2211
+ lastName: c.surname,
2212
+ emails: c.emailAddresses?.map((e) => e.address),
2213
+ mobile: c.mobilePhone,
2214
+ phones: c.businessPhones,
2215
+ company: c.companyName,
2216
+ jobTitle: c.jobTitle
2217
+ }));
2218
+ return jsonResult({ contacts, count: contacts.length });
2219
+ } catch (e) {
2220
+ return errorResult(e.message);
2221
+ }
2222
+ }
2223
+ },
2224
+ {
2225
+ name: "outlook_contacts_create",
2226
+ description: "Create a new contact in Outlook.",
2227
+ category: "utility",
2228
+ parameters: {
2229
+ type: "object",
2230
+ properties: {
2231
+ firstName: { type: "string", description: "First name" },
2232
+ lastName: { type: "string", description: "Last name" },
2233
+ email: { type: "string", description: "Email address" },
2234
+ mobile: { type: "string", description: "Mobile phone number" },
2235
+ company: { type: "string", description: "Company name" },
2236
+ jobTitle: { type: "string", description: "Job title" },
2237
+ notes: { type: "string", description: "Personal notes" }
2238
+ },
2239
+ required: ["firstName", "email"]
2240
+ },
2241
+ async execute(_id, params) {
2242
+ try {
2243
+ const token = await tp.getAccessToken();
2244
+ const contact = {
2245
+ givenName: params.firstName,
2246
+ emailAddresses: [{ address: params.email, name: `${params.firstName} ${params.lastName || ""}`.trim() }]
2247
+ };
2248
+ if (params.lastName) contact.surname = params.lastName;
2249
+ if (params.mobile) contact.mobilePhone = params.mobile;
2250
+ if (params.company) contact.companyName = params.company;
2251
+ if (params.jobTitle) contact.jobTitle = params.jobTitle;
2252
+ if (params.notes) contact.personalNotes = params.notes;
2253
+ const created = await graph(token, "/me/contacts", { method: "POST", body: contact });
2254
+ return jsonResult({ id: created.id, name: created.displayName, email: params.email });
2255
+ } catch (e) {
2256
+ return errorResult(e.message);
2257
+ }
2258
+ }
2259
+ },
2260
+ {
2261
+ name: "outlook_contacts_update",
2262
+ description: "Update an existing contact.",
2263
+ category: "utility",
2264
+ parameters: {
2265
+ type: "object",
2266
+ properties: {
2267
+ contactId: { type: "string", description: "Contact ID" },
2268
+ firstName: { type: "string", description: "First name" },
2269
+ lastName: { type: "string", description: "Last name" },
2270
+ email: { type: "string", description: "Email address" },
2271
+ mobile: { type: "string", description: "Mobile phone" },
2272
+ company: { type: "string", description: "Company name" },
2273
+ jobTitle: { type: "string", description: "Job title" }
2274
+ },
2275
+ required: ["contactId"]
2276
+ },
2277
+ async execute(_id, params) {
2278
+ try {
2279
+ const token = await tp.getAccessToken();
2280
+ const update = {};
2281
+ if (params.firstName) update.givenName = params.firstName;
2282
+ if (params.lastName) update.surname = params.lastName;
2283
+ if (params.email) update.emailAddresses = [{ address: params.email }];
2284
+ if (params.mobile) update.mobilePhone = params.mobile;
2285
+ if (params.company) update.companyName = params.company;
2286
+ if (params.jobTitle) update.jobTitle = params.jobTitle;
2287
+ await graph(token, `/me/contacts/${params.contactId}`, { method: "PATCH", body: update });
2288
+ return jsonResult({ updated: true, contactId: params.contactId });
2289
+ } catch (e) {
2290
+ return errorResult(e.message);
2291
+ }
2292
+ }
2293
+ },
2294
+ {
2295
+ name: "outlook_contacts_delete",
2296
+ description: "Delete a contact from Outlook.",
2297
+ category: "utility",
2298
+ parameters: {
2299
+ type: "object",
2300
+ properties: {
2301
+ contactId: { type: "string", description: "Contact ID to delete" }
2302
+ },
2303
+ required: ["contactId"]
2304
+ },
2305
+ async execute(_id, params) {
2306
+ try {
2307
+ const token = await tp.getAccessToken();
2308
+ await graph(token, `/me/contacts/${params.contactId}`, { method: "DELETE" });
2309
+ return jsonResult({ deleted: true, contactId: params.contactId });
2310
+ } catch (e) {
2311
+ return errorResult(e.message);
2312
+ }
2313
+ }
2314
+ },
2315
+ {
2316
+ name: "outlook_people_search",
2317
+ description: "Search for people relevant to the user (contacts, colleagues, recent correspondents).",
2318
+ category: "utility",
2319
+ parameters: {
2320
+ type: "object",
2321
+ properties: {
2322
+ query: { type: "string", description: "Search query (name or email)" },
2323
+ maxResults: { type: "number", description: "Max results (default: 10)" }
2324
+ },
2325
+ required: ["query"]
2326
+ },
2327
+ async execute(_id, params) {
2328
+ try {
2329
+ const token = await tp.getAccessToken();
2330
+ const data = await graph(token, "/me/people", {
2331
+ query: {
2332
+ "$search": `"${params.query}"`,
2333
+ "$top": String(params.maxResults || 10),
2334
+ "$select": "id,displayName,givenName,surname,emailAddresses,companyName,jobTitle,department"
2335
+ }
2336
+ });
2337
+ const people = (data.value || []).map((p) => ({
2338
+ id: p.id,
2339
+ name: p.displayName,
2340
+ firstName: p.givenName,
2341
+ lastName: p.surname,
2342
+ emails: p.emailAddresses?.map((e) => e.address),
2343
+ company: p.companyName,
2344
+ jobTitle: p.jobTitle,
2345
+ department: p.department
2346
+ }));
2347
+ return jsonResult({ people, count: people.length });
2348
+ } catch (e) {
2349
+ return errorResult(e.message);
2350
+ }
2351
+ }
2352
+ }
2353
+ ];
2354
+ }
2355
+
2356
+ // src/agent-tools/tools/microsoft/excel.ts
2357
+ function itemPath(p) {
2358
+ if (p.driveId && p.itemId) return `/drives/${p.driveId}/items/${p.itemId}`;
2359
+ if (p.itemId) return `/me/drive/items/${p.itemId}`;
2360
+ if (p.path) return `/me/drive/root:${p.path}:`;
2361
+ throw new Error("Provide itemId or path to the Excel file");
2362
+ }
2363
+ function createExcelTools(config, _options) {
2364
+ const tp = config.tokenProvider;
2365
+ return [
2366
+ {
2367
+ name: "excel_list_worksheets",
2368
+ description: "List all worksheets in an Excel workbook.",
2369
+ category: "utility",
2370
+ parameters: {
2371
+ type: "object",
2372
+ properties: {
2373
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID of the Excel file" },
2374
+ path: { type: "string", description: 'File path (alternative to itemId, e.g., "/Documents/Budget.xlsx")' },
2375
+ driveId: { type: "string", description: "Drive ID (for SharePoint drives)" }
2376
+ },
2377
+ required: []
2378
+ },
2379
+ async execute(_id, params) {
2380
+ try {
2381
+ const token = await tp.getAccessToken();
2382
+ const base = itemPath(params);
2383
+ const data = await graph(token, `${base}/workbook/worksheets`);
2384
+ const sheets = (data.value || []).map((s) => ({
2385
+ id: s.id,
2386
+ name: s.name,
2387
+ position: s.position,
2388
+ visibility: s.visibility
2389
+ }));
2390
+ return jsonResult({ worksheets: sheets, count: sheets.length });
2391
+ } catch (e) {
2392
+ return errorResult(e.message);
2393
+ }
2394
+ }
2395
+ },
2396
+ {
2397
+ name: "excel_read_range",
2398
+ description: "Read a range of cells from an Excel worksheet. Returns values, formulas, and formatting.",
2399
+ category: "utility",
2400
+ parameters: {
2401
+ type: "object",
2402
+ properties: {
2403
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2404
+ path: { type: "string", description: "File path (alternative to itemId)" },
2405
+ driveId: { type: "string", description: "Drive ID (for SharePoint)" },
2406
+ worksheet: { type: "string", description: "Worksheet name (default: first sheet)" },
2407
+ range: { type: "string", description: 'Cell range (e.g., "A1:D10", "Sheet1!A1:Z100"). Omit for used range.' }
2408
+ },
2409
+ required: []
2410
+ },
2411
+ async execute(_id, params) {
2412
+ try {
2413
+ const token = await tp.getAccessToken();
2414
+ const base = itemPath(params);
2415
+ let rangePath;
2416
+ if (params.range) {
2417
+ const ws = params.worksheet ? `/worksheets/${encodeURIComponent(params.worksheet)}` : "/worksheets";
2418
+ if (params.range.includes("!")) {
2419
+ rangePath = `${base}/workbook/worksheets/${encodeURIComponent(params.range.split("!")[0])}/range(address='${params.range.split("!")[1]}')`;
2420
+ } else if (params.worksheet) {
2421
+ rangePath = `${base}/workbook/worksheets/${encodeURIComponent(params.worksheet)}/range(address='${params.range}')`;
2422
+ } else {
2423
+ rangePath = `${base}/workbook/worksheets('Sheet1')/range(address='${params.range}')`;
2424
+ }
2425
+ } else {
2426
+ const ws = params.worksheet || "Sheet1";
2427
+ rangePath = `${base}/workbook/worksheets/${encodeURIComponent(ws)}/usedRange`;
2428
+ }
2429
+ const data = await graph(token, rangePath, {
2430
+ query: { "$select": "address,values,text,formulas,numberFormat,rowCount,columnCount" }
2431
+ });
2432
+ return jsonResult({
2433
+ address: data.address,
2434
+ values: data.values,
2435
+ text: data.text,
2436
+ formulas: data.formulas,
2437
+ rows: data.rowCount,
2438
+ columns: data.columnCount
2439
+ });
2440
+ } catch (e) {
2441
+ return errorResult(e.message);
2442
+ }
2443
+ }
2444
+ },
2445
+ {
2446
+ name: "excel_write_range",
2447
+ description: "Write values to a range of cells in an Excel worksheet.",
2448
+ category: "utility",
2449
+ parameters: {
2450
+ type: "object",
2451
+ properties: {
2452
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2453
+ path: { type: "string", description: "File path (alternative to itemId)" },
2454
+ driveId: { type: "string", description: "Drive ID (for SharePoint)" },
2455
+ worksheet: { type: "string", description: "Worksheet name (default: Sheet1)" },
2456
+ range: { type: "string", description: 'Cell range to write to (e.g., "A1:C3")' },
2457
+ values: { type: "array", description: 'Array of arrays \u2014 each inner array is a row (e.g., [["Name","Age"],["Alice",30]])' }
2458
+ },
2459
+ required: ["range", "values"]
2460
+ },
2461
+ async execute(_id, params) {
2462
+ try {
2463
+ const token = await tp.getAccessToken();
2464
+ const base = itemPath(params);
2465
+ const ws = params.worksheet || "Sheet1";
2466
+ await graph(token, `${base}/workbook/worksheets/${encodeURIComponent(ws)}/range(address='${params.range}')`, {
2467
+ method: "PATCH",
2468
+ body: { values: params.values }
2469
+ });
2470
+ return jsonResult({ written: true, range: params.range, worksheet: ws });
2471
+ } catch (e) {
2472
+ return errorResult(e.message);
2473
+ }
2474
+ }
2475
+ },
2476
+ {
2477
+ name: "excel_add_row",
2478
+ description: "Add a row to an Excel table (structured table, not just a range).",
2479
+ category: "utility",
2480
+ parameters: {
2481
+ type: "object",
2482
+ properties: {
2483
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2484
+ path: { type: "string", description: "File path" },
2485
+ driveId: { type: "string", description: "Drive ID" },
2486
+ worksheet: { type: "string", description: "Worksheet name" },
2487
+ tableName: { type: "string", description: "Table name (default: first table found)" },
2488
+ values: { type: "array", description: 'Row values as array (e.g., ["Alice", 30, "alice@example.com"])' }
2489
+ },
2490
+ required: ["values"]
2491
+ },
2492
+ async execute(_id, params) {
2493
+ try {
2494
+ const token = await tp.getAccessToken();
2495
+ const base = itemPath(params);
2496
+ let tablePath;
2497
+ if (params.tableName) {
2498
+ tablePath = params.worksheet ? `${base}/workbook/worksheets/${encodeURIComponent(params.worksheet)}/tables/${encodeURIComponent(params.tableName)}` : `${base}/workbook/tables/${encodeURIComponent(params.tableName)}`;
2499
+ } else {
2500
+ const ws = params.worksheet || "Sheet1";
2501
+ const tables = await graph(token, `${base}/workbook/worksheets/${encodeURIComponent(ws)}/tables`);
2502
+ if (!tables.value?.length) throw new Error("No tables found in worksheet. Create a table first or use excel_write_range.");
2503
+ tablePath = `${base}/workbook/tables/${tables.value[0].id}`;
2504
+ }
2505
+ const row = await graph(token, `${tablePath}/rows/add`, {
2506
+ method: "POST",
2507
+ body: { values: [params.values] }
2508
+ });
2509
+ return jsonResult({ added: true, index: row.index });
2510
+ } catch (e) {
2511
+ return errorResult(e.message);
2512
+ }
2513
+ }
2514
+ },
2515
+ {
2516
+ name: "excel_list_tables",
2517
+ description: "List all tables in an Excel workbook or specific worksheet.",
2518
+ category: "utility",
2519
+ parameters: {
2520
+ type: "object",
2521
+ properties: {
2522
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2523
+ path: { type: "string", description: "File path" },
2524
+ driveId: { type: "string", description: "Drive ID" },
2525
+ worksheet: { type: "string", description: "Worksheet name (omit for all tables in workbook)" }
2526
+ },
2527
+ required: []
2528
+ },
2529
+ async execute(_id, params) {
2530
+ try {
2531
+ const token = await tp.getAccessToken();
2532
+ const base = itemPath(params);
2533
+ const tablesPath = params.worksheet ? `${base}/workbook/worksheets/${encodeURIComponent(params.worksheet)}/tables` : `${base}/workbook/tables`;
2534
+ const data = await graph(token, tablesPath);
2535
+ const tables = (data.value || []).map((t) => ({
2536
+ id: t.id,
2537
+ name: t.name,
2538
+ rows: t.rowCount,
2539
+ columns: t.columns?.length,
2540
+ showHeaders: t.showHeaders,
2541
+ style: t.style
2542
+ }));
2543
+ return jsonResult({ tables, count: tables.length });
2544
+ } catch (e) {
2545
+ return errorResult(e.message);
2546
+ }
2547
+ }
2548
+ },
2549
+ {
2550
+ name: "excel_read_table",
2551
+ description: "Read all data from an Excel table including headers.",
2552
+ category: "utility",
2553
+ parameters: {
2554
+ type: "object",
2555
+ properties: {
2556
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2557
+ path: { type: "string", description: "File path" },
2558
+ driveId: { type: "string", description: "Drive ID" },
2559
+ tableName: { type: "string", description: "Table name or ID" }
2560
+ },
2561
+ required: ["tableName"]
2562
+ },
2563
+ async execute(_id, params) {
2564
+ try {
2565
+ const token = await tp.getAccessToken();
2566
+ const base = itemPath(params);
2567
+ const [headerData, bodyData] = await Promise.all([
2568
+ graph(token, `${base}/workbook/tables/${encodeURIComponent(params.tableName)}/headerRowRange`),
2569
+ graph(token, `${base}/workbook/tables/${encodeURIComponent(params.tableName)}/dataBodyRange`)
2570
+ ]);
2571
+ return jsonResult({
2572
+ headers: headerData.values?.[0],
2573
+ rows: bodyData.values,
2574
+ rowCount: bodyData.rowCount,
2575
+ columnCount: bodyData.columnCount
2576
+ });
2577
+ } catch (e) {
2578
+ return errorResult(e.message);
2579
+ }
2580
+ }
2581
+ },
2582
+ {
2583
+ name: "excel_create_worksheet",
2584
+ description: "Add a new worksheet to an Excel workbook.",
2585
+ category: "utility",
2586
+ parameters: {
2587
+ type: "object",
2588
+ properties: {
2589
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2590
+ path: { type: "string", description: "File path" },
2591
+ driveId: { type: "string", description: "Drive ID" },
2592
+ name: { type: "string", description: "Worksheet name" }
2593
+ },
2594
+ required: ["name"]
2595
+ },
2596
+ async execute(_id, params) {
2597
+ try {
2598
+ const token = await tp.getAccessToken();
2599
+ const base = itemPath(params);
2600
+ const ws = await graph(token, `${base}/workbook/worksheets/add`, {
2601
+ method: "POST",
2602
+ body: { name: params.name }
2603
+ });
2604
+ return jsonResult({ id: ws.id, name: ws.name, position: ws.position });
2605
+ } catch (e) {
2606
+ return errorResult(e.message);
2607
+ }
2608
+ }
2609
+ }
2610
+ ];
2611
+ }
2612
+
2613
+ // src/agent-tools/tools/microsoft/excel-vba.ts
2614
+ function itemPath2(p) {
2615
+ if (p.driveId && p.itemId) return `/drives/${p.driveId}/items/${p.itemId}`;
2616
+ if (p.itemId) return `/me/drive/items/${p.itemId}`;
2617
+ if (p.path) return `/me/drive/root:${p.path}:`;
2618
+ throw new Error("Provide itemId or path");
2619
+ }
2620
+ function createExcelAdvancedTools(config, _options) {
2621
+ const tp = config.tokenProvider;
2622
+ return [
2623
+ {
2624
+ name: "excel_create_session",
2625
+ description: "Create a workbook session for batch operations. Returns a session ID to use in subsequent calls for atomic transactions.",
2626
+ category: "utility",
2627
+ parameters: {
2628
+ type: "object",
2629
+ properties: {
2630
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2631
+ path: { type: "string", description: "File path" },
2632
+ driveId: { type: "string", description: "Drive ID" },
2633
+ persistChanges: { type: "boolean", description: "Persist changes after session closes (default: true)" }
2634
+ },
2635
+ required: []
2636
+ },
2637
+ async execute(_id, params) {
2638
+ try {
2639
+ const token = await tp.getAccessToken();
2640
+ const base = itemPath2(params);
2641
+ const session = await graph(token, `${base}/workbook/createSession`, {
2642
+ method: "POST",
2643
+ body: { persistChanges: params.persistChanges !== false }
2644
+ });
2645
+ return jsonResult({ sessionId: session.id, persistChanges: session.persistChanges });
2646
+ } catch (e) {
2647
+ return errorResult(e.message);
2648
+ }
2649
+ }
2650
+ },
2651
+ {
2652
+ name: "excel_close_session",
2653
+ description: "Close a workbook session. Commits or discards changes based on session config.",
2654
+ category: "utility",
2655
+ parameters: {
2656
+ type: "object",
2657
+ properties: {
2658
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2659
+ path: { type: "string", description: "File path" },
2660
+ driveId: { type: "string", description: "Drive ID" },
2661
+ sessionId: { type: "string", description: "Session ID to close" }
2662
+ },
2663
+ required: ["sessionId"]
2664
+ },
2665
+ async execute(_id, params) {
2666
+ try {
2667
+ const token = await tp.getAccessToken();
2668
+ const base = itemPath2(params);
2669
+ await graph(token, `${base}/workbook/closeSession`, {
2670
+ method: "POST",
2671
+ headers: { "workbook-session-id": params.sessionId }
2672
+ });
2673
+ return jsonResult({ closed: true, sessionId: params.sessionId });
2674
+ } catch (e) {
2675
+ return errorResult(e.message);
2676
+ }
2677
+ }
2678
+ },
2679
+ {
2680
+ name: "excel_evaluate_formula",
2681
+ description: "Evaluate an Excel formula without writing it to a cell. Supports any Excel function (SUM, VLOOKUP, IF, etc.).",
2682
+ category: "utility",
2683
+ parameters: {
2684
+ type: "object",
2685
+ properties: {
2686
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2687
+ path: { type: "string", description: "File path" },
2688
+ driveId: { type: "string", description: "Drive ID" },
2689
+ worksheet: { type: "string", description: "Worksheet name (default: Sheet1)" },
2690
+ formula: { type: "string", description: 'Excel formula (e.g., "=SUM(A1:A10)", "=VLOOKUP("Alice",A:C,3,FALSE)")' },
2691
+ sessionId: { type: "string", description: "Optional session ID for batch operations" }
2692
+ },
2693
+ required: ["formula"]
2694
+ },
2695
+ async execute(_id, params) {
2696
+ try {
2697
+ const token = await tp.getAccessToken();
2698
+ const base = itemPath2(params);
2699
+ const ws = params.worksheet || "Sheet1";
2700
+ const headers = {};
2701
+ if (params.sessionId) headers["workbook-session-id"] = params.sessionId;
2702
+ const tempCell = "ZZ9999";
2703
+ await graph(token, `${base}/workbook/worksheets/${encodeURIComponent(ws)}/range(address='${tempCell}')`, {
2704
+ method: "PATCH",
2705
+ body: { formulas: [[params.formula]] },
2706
+ headers
2707
+ });
2708
+ const result = await graph(token, `${base}/workbook/worksheets/${encodeURIComponent(ws)}/range(address='${tempCell}')`, {
2709
+ query: { "$select": "values,text,formulas" },
2710
+ headers
2711
+ });
2712
+ await graph(token, `${base}/workbook/worksheets/${encodeURIComponent(ws)}/range(address='${tempCell}')/clear`, {
2713
+ method: "POST",
2714
+ body: { applyTo: "All" },
2715
+ headers
2716
+ });
2717
+ return jsonResult({
2718
+ formula: params.formula,
2719
+ result: result.values?.[0]?.[0],
2720
+ text: result.text?.[0]?.[0]
2721
+ });
2722
+ } catch (e) {
2723
+ return errorResult(e.message);
2724
+ }
2725
+ }
2726
+ },
2727
+ {
2728
+ name: "excel_named_ranges",
2729
+ description: "List all named ranges in a workbook.",
2730
+ category: "utility",
2731
+ parameters: {
2732
+ type: "object",
2733
+ properties: {
2734
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2735
+ path: { type: "string", description: "File path" },
2736
+ driveId: { type: "string", description: "Drive ID" }
2737
+ },
2738
+ required: []
2739
+ },
2740
+ async execute(_id, params) {
2741
+ try {
2742
+ const token = await tp.getAccessToken();
2743
+ const base = itemPath2(params);
2744
+ const data = await graph(token, `${base}/workbook/names`);
2745
+ const names = (data.value || []).map((n) => ({
2746
+ name: n.name,
2747
+ value: n.value,
2748
+ type: n.type,
2749
+ visible: n.visible,
2750
+ comment: n.comment
2751
+ }));
2752
+ return jsonResult({ namedRanges: names, count: names.length });
2753
+ } catch (e) {
2754
+ return errorResult(e.message);
2755
+ }
2756
+ }
2757
+ },
2758
+ {
2759
+ name: "excel_read_named_range",
2760
+ description: "Read values from a named range.",
2761
+ category: "utility",
2762
+ parameters: {
2763
+ type: "object",
2764
+ properties: {
2765
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2766
+ path: { type: "string", description: "File path" },
2767
+ driveId: { type: "string", description: "Drive ID" },
2768
+ name: { type: "string", description: "Named range name" }
2769
+ },
2770
+ required: ["name"]
2771
+ },
2772
+ async execute(_id, params) {
2773
+ try {
2774
+ const token = await tp.getAccessToken();
2775
+ const base = itemPath2(params);
2776
+ const range = await graph(token, `${base}/workbook/names/${encodeURIComponent(params.name)}/range`, {
2777
+ query: { "$select": "address,values,text,formulas,rowCount,columnCount" }
2778
+ });
2779
+ return jsonResult({
2780
+ name: params.name,
2781
+ address: range.address,
2782
+ values: range.values,
2783
+ text: range.text,
2784
+ rows: range.rowCount,
2785
+ columns: range.columnCount
2786
+ });
2787
+ } catch (e) {
2788
+ return errorResult(e.message);
2789
+ }
2790
+ }
2791
+ },
2792
+ {
2793
+ name: "excel_list_charts",
2794
+ description: "List all charts in an Excel worksheet.",
2795
+ category: "utility",
2796
+ parameters: {
2797
+ type: "object",
2798
+ properties: {
2799
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2800
+ path: { type: "string", description: "File path" },
2801
+ driveId: { type: "string", description: "Drive ID" },
2802
+ worksheet: { type: "string", description: "Worksheet name (default: Sheet1)" }
2803
+ },
2804
+ required: []
2805
+ },
2806
+ async execute(_id, params) {
2807
+ try {
2808
+ const token = await tp.getAccessToken();
2809
+ const base = itemPath2(params);
2810
+ const ws = params.worksheet || "Sheet1";
2811
+ const data = await graph(token, `${base}/workbook/worksheets/${encodeURIComponent(ws)}/charts`);
2812
+ const charts = (data.value || []).map((c) => ({
2813
+ id: c.id,
2814
+ name: c.name,
2815
+ height: c.height,
2816
+ width: c.width,
2817
+ top: c.top,
2818
+ left: c.left
2819
+ }));
2820
+ return jsonResult({ charts, count: charts.length });
2821
+ } catch (e) {
2822
+ return errorResult(e.message);
2823
+ }
2824
+ }
2825
+ },
2826
+ {
2827
+ name: "excel_chart_image",
2828
+ description: "Get a chart as a base64 PNG image.",
2829
+ category: "utility",
2830
+ parameters: {
2831
+ type: "object",
2832
+ properties: {
2833
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2834
+ path: { type: "string", description: "File path" },
2835
+ driveId: { type: "string", description: "Drive ID" },
2836
+ worksheet: { type: "string", description: "Worksheet name" },
2837
+ chartName: { type: "string", description: "Chart name or ID" }
2838
+ },
2839
+ required: ["chartName"]
2840
+ },
2841
+ async execute(_id, params) {
2842
+ try {
2843
+ const token = await tp.getAccessToken();
2844
+ const base = itemPath2(params);
2845
+ const ws = params.worksheet || "Sheet1";
2846
+ const img = await graph(token, `${base}/workbook/worksheets/${encodeURIComponent(ws)}/charts/${encodeURIComponent(params.chartName)}/image`);
2847
+ return jsonResult({ chartName: params.chartName, base64Image: img.value, format: "png" });
2848
+ } catch (e) {
2849
+ return errorResult(e.message);
2850
+ }
2851
+ }
2852
+ },
2853
+ {
2854
+ name: "excel_pivot_refresh",
2855
+ description: "Refresh all pivot tables in a workbook by recalculating the workbook.",
2856
+ category: "utility",
2857
+ parameters: {
2858
+ type: "object",
2859
+ properties: {
2860
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2861
+ path: { type: "string", description: "File path" },
2862
+ driveId: { type: "string", description: "Drive ID" },
2863
+ sessionId: { type: "string", description: "Session ID (recommended for consistency)" }
2864
+ },
2865
+ required: []
2866
+ },
2867
+ async execute(_id, params) {
2868
+ try {
2869
+ const token = await tp.getAccessToken();
2870
+ const base = itemPath2(params);
2871
+ const headers = {};
2872
+ if (params.sessionId) headers["workbook-session-id"] = params.sessionId;
2873
+ await graph(token, `${base}/workbook/application/calculate`, {
2874
+ method: "POST",
2875
+ body: { calculationType: "Full" },
2876
+ headers
2877
+ });
2878
+ return jsonResult({ refreshed: true, calculationType: "Full" });
2879
+ } catch (e) {
2880
+ return errorResult(e.message);
2881
+ }
2882
+ }
2883
+ },
2884
+ {
2885
+ name: "excel_set_cell_format",
2886
+ description: "Set number format, font, fill, or borders on a cell range.",
2887
+ category: "utility",
2888
+ parameters: {
2889
+ type: "object",
2890
+ properties: {
2891
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
2892
+ path: { type: "string", description: "File path" },
2893
+ driveId: { type: "string", description: "Drive ID" },
2894
+ worksheet: { type: "string", description: "Worksheet name" },
2895
+ range: { type: "string", description: 'Cell range (e.g., "A1:D10")' },
2896
+ numberFormat: { type: "string", description: 'Number format (e.g., "$#,##0.00", "0%", "mm/dd/yyyy")' },
2897
+ bold: { type: "boolean", description: "Set font bold" },
2898
+ italic: { type: "boolean", description: "Set font italic" },
2899
+ fontColor: { type: "string", description: 'Font color (e.g., "#FF0000")' },
2900
+ fillColor: { type: "string", description: 'Cell fill color (e.g., "#FFFF00")' },
2901
+ horizontalAlignment: { type: "string", description: "Left, Center, Right, Fill, Justify" }
2902
+ },
2903
+ required: ["range"]
2904
+ },
2905
+ async execute(_id, params) {
2906
+ try {
2907
+ const token = await tp.getAccessToken();
2908
+ const base = itemPath2(params);
2909
+ const ws = params.worksheet || "Sheet1";
2910
+ const rangePath = `${base}/workbook/worksheets/${encodeURIComponent(ws)}/range(address='${params.range}')`;
2911
+ const format = {};
2912
+ if (params.numberFormat) {
2913
+ await graph(token, `${rangePath}/format`, { method: "PATCH", body: { columnWidth: null } });
2914
+ }
2915
+ if (params.bold !== void 0 || params.italic !== void 0 || params.fontColor) {
2916
+ const font = {};
2917
+ if (params.bold !== void 0) font.bold = params.bold;
2918
+ if (params.italic !== void 0) font.italic = params.italic;
2919
+ if (params.fontColor) font.color = params.fontColor;
2920
+ await graph(token, `${rangePath}/format/font`, { method: "PATCH", body: font });
2921
+ }
2922
+ if (params.fillColor) {
2923
+ await graph(token, `${rangePath}/format/fill`, { method: "PATCH", body: { color: params.fillColor } });
2924
+ }
2925
+ if (params.horizontalAlignment) {
2926
+ await graph(token, `${rangePath}/format`, { method: "PATCH", body: { horizontalAlignment: params.horizontalAlignment } });
2927
+ }
2928
+ if (params.numberFormat) {
2929
+ const rangeInfo = await graph(token, rangePath, { query: { "$select": "rowCount,columnCount" } });
2930
+ const fmt = Array(rangeInfo.rowCount).fill(null).map(() => Array(rangeInfo.columnCount).fill(params.numberFormat));
2931
+ await graph(token, rangePath, { method: "PATCH", body: { numberFormat: fmt } });
2932
+ }
2933
+ return jsonResult({ formatted: true, range: params.range });
2934
+ } catch (e) {
2935
+ return errorResult(e.message);
2936
+ }
2937
+ }
2938
+ }
2939
+ ];
2940
+ }
2941
+
2942
+ // src/agent-tools/tools/microsoft/sharepoint.ts
2943
+ function createSharePointTools(config, _options) {
2944
+ const tp = config.tokenProvider;
2945
+ return [
2946
+ {
2947
+ name: "sharepoint_list_sites",
2948
+ description: "Search or list SharePoint sites the agent has access to.",
2949
+ category: "utility",
2950
+ parameters: {
2951
+ type: "object",
2952
+ properties: {
2953
+ search: { type: "string", description: "Search query for site name/description" },
2954
+ maxResults: { type: "number", description: "Max results (default: 20)" }
2955
+ },
2956
+ required: []
2957
+ },
2958
+ async execute(_id, params) {
2959
+ try {
2960
+ const token = await tp.getAccessToken();
2961
+ let path = "/sites";
2962
+ const query = { "$top": String(params.maxResults || 20) };
2963
+ if (params.search) {
2964
+ query["$search"] = `"${params.search}"`;
2965
+ }
2966
+ const data = await graph(token, path, { query });
2967
+ const sites = (data.value || []).map((s) => ({
2968
+ id: s.id,
2969
+ name: s.displayName,
2970
+ description: s.description,
2971
+ webUrl: s.webUrl,
2972
+ created: s.createdDateTime
2973
+ }));
2974
+ return jsonResult({ sites, count: sites.length });
2975
+ } catch (e) {
2976
+ return errorResult(e.message);
2977
+ }
2978
+ }
2979
+ },
2980
+ {
2981
+ name: "sharepoint_get_site",
2982
+ description: "Get details about a SharePoint site by hostname and path, or by site ID.",
2983
+ category: "utility",
2984
+ parameters: {
2985
+ type: "object",
2986
+ properties: {
2987
+ siteId: { type: "string", description: "Site ID" },
2988
+ hostname: { type: "string", description: 'Site hostname (e.g., "contoso.sharepoint.com")' },
2989
+ path: { type: "string", description: 'Site path (e.g., "/sites/engineering")' }
2990
+ },
2991
+ required: []
2992
+ },
2993
+ async execute(_id, params) {
2994
+ try {
2995
+ const token = await tp.getAccessToken();
2996
+ let sitePath;
2997
+ if (params.siteId) {
2998
+ sitePath = `/sites/${params.siteId}`;
2999
+ } else if (params.hostname) {
3000
+ sitePath = `/sites/${params.hostname}:${params.path || "/"}`;
3001
+ } else {
3002
+ throw new Error("Provide siteId or hostname");
3003
+ }
3004
+ const site = await graph(token, sitePath);
3005
+ return jsonResult({
3006
+ id: site.id,
3007
+ name: site.displayName,
3008
+ description: site.description,
3009
+ webUrl: site.webUrl,
3010
+ created: site.createdDateTime
3011
+ });
3012
+ } catch (e) {
3013
+ return errorResult(e.message);
3014
+ }
3015
+ }
3016
+ },
3017
+ {
3018
+ name: "sharepoint_list_drives",
3019
+ description: "List document libraries (drives) on a SharePoint site.",
3020
+ category: "utility",
3021
+ parameters: {
3022
+ type: "object",
3023
+ properties: {
3024
+ siteId: { type: "string", description: "SharePoint site ID" }
3025
+ },
3026
+ required: ["siteId"]
3027
+ },
3028
+ async execute(_id, params) {
3029
+ try {
3030
+ const token = await tp.getAccessToken();
3031
+ const data = await graph(token, `/sites/${params.siteId}/drives`);
3032
+ const drives = (data.value || []).map((d) => ({
3033
+ id: d.id,
3034
+ name: d.name,
3035
+ description: d.description,
3036
+ driveType: d.driveType,
3037
+ webUrl: d.webUrl,
3038
+ totalSize: d.quota?.total,
3039
+ usedSize: d.quota?.used
3040
+ }));
3041
+ return jsonResult({ drives, count: drives.length });
3042
+ } catch (e) {
3043
+ return errorResult(e.message);
3044
+ }
3045
+ }
3046
+ },
3047
+ {
3048
+ name: "sharepoint_list_files",
3049
+ description: "List files and folders in a SharePoint document library.",
3050
+ category: "utility",
3051
+ parameters: {
3052
+ type: "object",
3053
+ properties: {
3054
+ siteId: { type: "string", description: "SharePoint site ID" },
3055
+ driveId: { type: "string", description: "Drive (document library) ID" },
3056
+ path: { type: "string", description: "Folder path within the drive (default: root)" },
3057
+ maxResults: { type: "number", description: "Max items (default: 50)" }
3058
+ },
3059
+ required: ["siteId", "driveId"]
3060
+ },
3061
+ async execute(_id, params) {
3062
+ try {
3063
+ const token = await tp.getAccessToken();
3064
+ const basePath = params.path ? `/drives/${params.driveId}/root:${params.path}:/children` : `/drives/${params.driveId}/root/children`;
3065
+ const data = await graph(token, basePath, {
3066
+ query: {
3067
+ "$top": String(params.maxResults || 50),
3068
+ "$select": "id,name,size,createdDateTime,lastModifiedDateTime,webUrl,folder,file"
3069
+ }
3070
+ });
3071
+ const items = (data.value || []).map((i) => ({
3072
+ id: i.id,
3073
+ name: i.name,
3074
+ size: i.size,
3075
+ type: i.folder ? "folder" : "file",
3076
+ mimeType: i.file?.mimeType,
3077
+ childCount: i.folder?.childCount,
3078
+ created: i.createdDateTime,
3079
+ modified: i.lastModifiedDateTime,
3080
+ webUrl: i.webUrl
3081
+ }));
3082
+ return jsonResult({ items, count: items.length });
3083
+ } catch (e) {
3084
+ return errorResult(e.message);
3085
+ }
3086
+ }
3087
+ },
3088
+ {
3089
+ name: "sharepoint_upload_file",
3090
+ description: "Upload a text file to a SharePoint document library.",
3091
+ category: "utility",
3092
+ parameters: {
3093
+ type: "object",
3094
+ properties: {
3095
+ driveId: { type: "string", description: "Drive (document library) ID" },
3096
+ path: { type: "string", description: 'Destination path (e.g., "/General/report.md")' },
3097
+ content: { type: "string", description: "File content (text)" }
3098
+ },
3099
+ required: ["driveId", "path", "content"]
3100
+ },
3101
+ async execute(_id, params) {
3102
+ try {
3103
+ const token = await tp.getAccessToken();
3104
+ const res = await fetch(`https://graph.microsoft.com/v1.0/drives/${params.driveId}/root:${params.path}:/content`, {
3105
+ method: "PUT",
3106
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "text/plain" },
3107
+ body: params.content
3108
+ });
3109
+ if (!res.ok) throw new Error(`Upload failed: ${res.status} ${await res.text()}`);
3110
+ const data = await res.json();
3111
+ return jsonResult({ id: data.id, name: data.name, size: data.size, webUrl: data.webUrl });
3112
+ } catch (e) {
3113
+ return errorResult(e.message);
3114
+ }
3115
+ }
3116
+ },
3117
+ {
3118
+ name: "sharepoint_list_lists",
3119
+ description: "List SharePoint lists on a site.",
3120
+ category: "utility",
3121
+ parameters: {
3122
+ type: "object",
3123
+ properties: {
3124
+ siteId: { type: "string", description: "SharePoint site ID" }
3125
+ },
3126
+ required: ["siteId"]
3127
+ },
3128
+ async execute(_id, params) {
3129
+ try {
3130
+ const token = await tp.getAccessToken();
3131
+ const data = await graph(token, `/sites/${params.siteId}/lists`, {
3132
+ query: { "$select": "id,displayName,description,webUrl,list", "$top": "50" }
3133
+ });
3134
+ const lists = (data.value || []).map((l) => ({
3135
+ id: l.id,
3136
+ name: l.displayName,
3137
+ description: l.description,
3138
+ webUrl: l.webUrl,
3139
+ template: l.list?.template,
3140
+ hidden: l.list?.hidden
3141
+ }));
3142
+ return jsonResult({ lists: lists.filter((l) => !l.hidden), count: lists.length });
3143
+ } catch (e) {
3144
+ return errorResult(e.message);
3145
+ }
3146
+ }
3147
+ },
3148
+ {
3149
+ name: "sharepoint_list_items",
3150
+ description: "Read items from a SharePoint list.",
3151
+ category: "utility",
3152
+ parameters: {
3153
+ type: "object",
3154
+ properties: {
3155
+ siteId: { type: "string", description: "SharePoint site ID" },
3156
+ listId: { type: "string", description: "List ID or name" },
3157
+ maxResults: { type: "number", description: "Max items (default: 50)" },
3158
+ filter: { type: "string", description: "OData $filter expression" },
3159
+ expand: { type: "boolean", description: "Expand field values (default: true)" }
3160
+ },
3161
+ required: ["siteId", "listId"]
3162
+ },
3163
+ async execute(_id, params) {
3164
+ try {
3165
+ const token = await tp.getAccessToken();
3166
+ const query = {
3167
+ "$top": String(params.maxResults || 50)
3168
+ };
3169
+ if (params.expand !== false) query["$expand"] = "fields";
3170
+ if (params.filter) query["$filter"] = params.filter;
3171
+ const data = await graph(token, `/sites/${params.siteId}/lists/${params.listId}/items`, { query });
3172
+ const items = (data.value || []).map((i) => ({
3173
+ id: i.id,
3174
+ created: i.createdDateTime,
3175
+ modified: i.lastModifiedDateTime,
3176
+ webUrl: i.webUrl,
3177
+ fields: i.fields
3178
+ }));
3179
+ return jsonResult({ items, count: items.length });
3180
+ } catch (e) {
3181
+ return errorResult(e.message);
3182
+ }
3183
+ }
3184
+ },
3185
+ {
3186
+ name: "sharepoint_create_list_item",
3187
+ description: "Create a new item in a SharePoint list.",
3188
+ category: "utility",
3189
+ parameters: {
3190
+ type: "object",
3191
+ properties: {
3192
+ siteId: { type: "string", description: "SharePoint site ID" },
3193
+ listId: { type: "string", description: "List ID or name" },
3194
+ fields: { type: "object", description: 'Field values as key-value pairs (e.g., {"Title": "My Item", "Status": "Active"})' }
3195
+ },
3196
+ required: ["siteId", "listId", "fields"]
3197
+ },
3198
+ async execute(_id, params) {
3199
+ try {
3200
+ const token = await tp.getAccessToken();
3201
+ const item = await graph(token, `/sites/${params.siteId}/lists/${params.listId}/items`, {
3202
+ method: "POST",
3203
+ body: { fields: params.fields }
3204
+ });
3205
+ return jsonResult({ id: item.id, created: true, fields: item.fields });
3206
+ } catch (e) {
3207
+ return errorResult(e.message);
3208
+ }
3209
+ }
3210
+ },
3211
+ {
3212
+ name: "sharepoint_update_list_item",
3213
+ description: "Update an existing item in a SharePoint list.",
3214
+ category: "utility",
3215
+ parameters: {
3216
+ type: "object",
3217
+ properties: {
3218
+ siteId: { type: "string", description: "SharePoint site ID" },
3219
+ listId: { type: "string", description: "List ID or name" },
3220
+ itemId: { type: "string", description: "Item ID to update" },
3221
+ fields: { type: "object", description: "Updated field values" }
3222
+ },
3223
+ required: ["siteId", "listId", "itemId", "fields"]
3224
+ },
3225
+ async execute(_id, params) {
3226
+ try {
3227
+ const token = await tp.getAccessToken();
3228
+ await graph(token, `/sites/${params.siteId}/lists/${params.listId}/items/${params.itemId}/fields`, {
3229
+ method: "PATCH",
3230
+ body: params.fields
3231
+ });
3232
+ return jsonResult({ updated: true, itemId: params.itemId });
3233
+ } catch (e) {
3234
+ return errorResult(e.message);
3235
+ }
3236
+ }
3237
+ },
3238
+ {
3239
+ name: "sharepoint_search",
3240
+ description: "Search across SharePoint for sites, files, list items, and pages.",
3241
+ category: "utility",
3242
+ parameters: {
3243
+ type: "object",
3244
+ properties: {
3245
+ query: { type: "string", description: "Search query" },
3246
+ entityTypes: { type: "string", description: "Comma-separated: site, drive, driveItem, list, listItem, message (default: driveItem)" },
3247
+ maxResults: { type: "number", description: "Max results (default: 10)" }
3248
+ },
3249
+ required: ["query"]
3250
+ },
3251
+ async execute(_id, params) {
3252
+ try {
3253
+ const token = await tp.getAccessToken();
3254
+ const types = (params.entityTypes || "driveItem").split(",").map((t) => t.trim());
3255
+ const data = await graph(token, "/search/query", {
3256
+ method: "POST",
3257
+ body: {
3258
+ requests: [{
3259
+ entityTypes: types,
3260
+ query: { queryString: params.query },
3261
+ from: 0,
3262
+ size: params.maxResults || 10
3263
+ }]
3264
+ }
3265
+ });
3266
+ const hits = data.value?.[0]?.hitsContainers?.[0]?.hits || [];
3267
+ const results = hits.map((h) => ({
3268
+ id: h.resource?.id,
3269
+ name: h.resource?.name || h.resource?.displayName,
3270
+ summary: h.summary,
3271
+ webUrl: h.resource?.webUrl,
3272
+ type: h.resource?.["@odata.type"],
3273
+ lastModified: h.resource?.lastModifiedDateTime
3274
+ }));
3275
+ return jsonResult({ results, count: results.length, query: params.query });
3276
+ } catch (e) {
3277
+ return errorResult(e.message);
3278
+ }
3279
+ }
3280
+ }
3281
+ ];
3282
+ }
3283
+
3284
+ // src/agent-tools/tools/microsoft/onenote.ts
3285
+ function createOneNoteTools(config, _options) {
3286
+ const tp = config.tokenProvider;
3287
+ return [
3288
+ {
3289
+ name: "onenote_list_notebooks",
3290
+ description: "List all OneNote notebooks.",
3291
+ category: "utility",
3292
+ parameters: { type: "object", properties: {}, required: [] },
3293
+ async execute(_id) {
3294
+ try {
3295
+ const token = await tp.getAccessToken();
3296
+ const data = await graph(token, "/me/onenote/notebooks", {
3297
+ query: { "$select": "id,displayName,createdDateTime,lastModifiedDateTime,isShared,links", "$orderby": "lastModifiedDateTime desc" }
3298
+ });
3299
+ const notebooks = (data.value || []).map((n) => ({
3300
+ id: n.id,
3301
+ name: n.displayName,
3302
+ created: n.createdDateTime,
3303
+ modified: n.lastModifiedDateTime,
3304
+ isShared: n.isShared,
3305
+ webUrl: n.links?.oneNoteWebUrl?.href
3306
+ }));
3307
+ return jsonResult({ notebooks, count: notebooks.length });
3308
+ } catch (e) {
3309
+ return errorResult(e.message);
3310
+ }
3311
+ }
3312
+ },
3313
+ {
3314
+ name: "onenote_list_sections",
3315
+ description: "List sections in a OneNote notebook.",
3316
+ category: "utility",
3317
+ parameters: {
3318
+ type: "object",
3319
+ properties: {
3320
+ notebookId: { type: "string", description: "Notebook ID" }
3321
+ },
3322
+ required: ["notebookId"]
3323
+ },
3324
+ async execute(_id, params) {
3325
+ try {
3326
+ const token = await tp.getAccessToken();
3327
+ const data = await graph(token, `/me/onenote/notebooks/${params.notebookId}/sections`, {
3328
+ query: { "$select": "id,displayName,createdDateTime,lastModifiedDateTime" }
3329
+ });
3330
+ const sections = (data.value || []).map((s) => ({
3331
+ id: s.id,
3332
+ name: s.displayName,
3333
+ created: s.createdDateTime,
3334
+ modified: s.lastModifiedDateTime
3335
+ }));
3336
+ return jsonResult({ sections, count: sections.length });
3337
+ } catch (e) {
3338
+ return errorResult(e.message);
3339
+ }
3340
+ }
3341
+ },
3342
+ {
3343
+ name: "onenote_list_pages",
3344
+ description: "List pages in a OneNote section.",
3345
+ category: "utility",
3346
+ parameters: {
3347
+ type: "object",
3348
+ properties: {
3349
+ sectionId: { type: "string", description: "Section ID" },
3350
+ maxResults: { type: "number", description: "Max pages (default: 20)" }
3351
+ },
3352
+ required: ["sectionId"]
3353
+ },
3354
+ async execute(_id, params) {
3355
+ try {
3356
+ const token = await tp.getAccessToken();
3357
+ const data = await graph(token, `/me/onenote/sections/${params.sectionId}/pages`, {
3358
+ query: {
3359
+ "$top": String(params.maxResults || 20),
3360
+ "$select": "id,title,createdDateTime,lastModifiedDateTime,links,level,order",
3361
+ "$orderby": "lastModifiedDateTime desc"
3362
+ }
3363
+ });
3364
+ const pages = (data.value || []).map((p) => ({
3365
+ id: p.id,
3366
+ title: p.title,
3367
+ created: p.createdDateTime,
3368
+ modified: p.lastModifiedDateTime,
3369
+ webUrl: p.links?.oneNoteWebUrl?.href
3370
+ }));
3371
+ return jsonResult({ pages, count: pages.length });
3372
+ } catch (e) {
3373
+ return errorResult(e.message);
3374
+ }
3375
+ }
3376
+ },
3377
+ {
3378
+ name: "onenote_read_page",
3379
+ description: "Read the HTML content of a OneNote page.",
3380
+ category: "utility",
3381
+ parameters: {
3382
+ type: "object",
3383
+ properties: {
3384
+ pageId: { type: "string", description: "Page ID" }
3385
+ },
3386
+ required: ["pageId"]
3387
+ },
3388
+ async execute(_id, params) {
3389
+ try {
3390
+ const token = await tp.getAccessToken();
3391
+ const res = await fetch(`https://graph.microsoft.com/v1.0/me/onenote/pages/${params.pageId}/content`, {
3392
+ headers: { Authorization: `Bearer ${token}` }
3393
+ });
3394
+ if (!res.ok) throw new Error(`OneNote API ${res.status}: ${await res.text()}`);
3395
+ const html = await res.text();
3396
+ const text = html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
3397
+ return jsonResult({ pageId: params.pageId, html, text: text.substring(0, 1e4) });
3398
+ } catch (e) {
3399
+ return errorResult(e.message);
3400
+ }
3401
+ }
3402
+ },
3403
+ {
3404
+ name: "onenote_create_page",
3405
+ description: "Create a new page in a OneNote section. Content is HTML.",
3406
+ category: "utility",
3407
+ parameters: {
3408
+ type: "object",
3409
+ properties: {
3410
+ sectionId: { type: "string", description: "Section ID to create the page in" },
3411
+ title: { type: "string", description: "Page title" },
3412
+ content: { type: "string", description: "Page content (HTML or plain text)" }
3413
+ },
3414
+ required: ["sectionId", "title", "content"]
3415
+ },
3416
+ async execute(_id, params) {
3417
+ try {
3418
+ const token = await tp.getAccessToken();
3419
+ const html = `<!DOCTYPE html><html><head><title>${params.title}</title></head><body>${params.content}</body></html>`;
3420
+ const res = await fetch(`https://graph.microsoft.com/v1.0/me/onenote/sections/${params.sectionId}/pages`, {
3421
+ method: "POST",
3422
+ headers: {
3423
+ Authorization: `Bearer ${token}`,
3424
+ "Content-Type": "application/xhtml+xml"
3425
+ },
3426
+ body: html
3427
+ });
3428
+ if (!res.ok) throw new Error(`OneNote API ${res.status}: ${await res.text()}`);
3429
+ const page = await res.json();
3430
+ return jsonResult({
3431
+ id: page.id,
3432
+ title: page.title,
3433
+ webUrl: page.links?.oneNoteWebUrl?.href,
3434
+ created: true
3435
+ });
3436
+ } catch (e) {
3437
+ return errorResult(e.message);
3438
+ }
3439
+ }
3440
+ },
3441
+ {
3442
+ name: "onenote_update_page",
3443
+ description: "Append content to an existing OneNote page.",
3444
+ category: "utility",
3445
+ parameters: {
3446
+ type: "object",
3447
+ properties: {
3448
+ pageId: { type: "string", description: "Page ID" },
3449
+ content: { type: "string", description: "HTML content to append" },
3450
+ position: { type: "string", description: "Where to add: after (end of body) or before (start of body). Default: after" }
3451
+ },
3452
+ required: ["pageId", "content"]
3453
+ },
3454
+ async execute(_id, params) {
3455
+ try {
3456
+ const token = await tp.getAccessToken();
3457
+ await graph(token, `/me/onenote/pages/${params.pageId}/content`, {
3458
+ method: "PATCH",
3459
+ body: [{
3460
+ target: "body",
3461
+ action: params.position === "before" ? "prepend" : "append",
3462
+ content: params.content
3463
+ }]
3464
+ });
3465
+ return jsonResult({ updated: true, pageId: params.pageId });
3466
+ } catch (e) {
3467
+ return errorResult(e.message);
3468
+ }
3469
+ }
3470
+ }
3471
+ ];
3472
+ }
3473
+
3474
+ // src/agent-tools/tools/microsoft/powerpoint.ts
3475
+ function itemPath3(p) {
3476
+ if (p.driveId && p.itemId) return `/drives/${p.driveId}/items/${p.itemId}`;
3477
+ if (p.itemId) return `/me/drive/items/${p.itemId}`;
3478
+ if (p.path) return `/me/drive/root:${p.path}:`;
3479
+ throw new Error("Provide itemId or path to the PowerPoint file");
3480
+ }
3481
+ function createPowerPointTools(config, _options) {
3482
+ const tp = config.tokenProvider;
3483
+ return [
3484
+ {
3485
+ name: "powerpoint_get_info",
3486
+ description: "Get metadata and slide count of a PowerPoint presentation.",
3487
+ category: "utility",
3488
+ parameters: {
3489
+ type: "object",
3490
+ properties: {
3491
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
3492
+ path: { type: "string", description: 'File path (e.g., "/Documents/Deck.pptx")' },
3493
+ driveId: { type: "string", description: "Drive ID (for SharePoint)" }
3494
+ },
3495
+ required: []
3496
+ },
3497
+ async execute(_id, params) {
3498
+ try {
3499
+ const token = await tp.getAccessToken();
3500
+ const base = itemPath3(params);
3501
+ const meta = await graph(token, base, {
3502
+ query: { "$select": "id,name,size,webUrl,createdDateTime,lastModifiedDateTime,file" }
3503
+ });
3504
+ let slideCount = null;
3505
+ try {
3506
+ const preview = await graph(token, `${base}/preview`, { method: "POST", body: {} });
3507
+ const thumbs = await graph(token, `${base}/thumbnails`);
3508
+ if (thumbs.value?.[0]) slideCount = Object.keys(thumbs.value[0]).filter((k) => k !== "id").length;
3509
+ } catch {
3510
+ }
3511
+ return jsonResult({
3512
+ id: meta.id,
3513
+ name: meta.name,
3514
+ size: meta.size,
3515
+ webUrl: meta.webUrl,
3516
+ mimeType: meta.file?.mimeType,
3517
+ created: meta.createdDateTime,
3518
+ modified: meta.lastModifiedDateTime,
3519
+ slideCount
3520
+ });
3521
+ } catch (e) {
3522
+ return errorResult(e.message);
3523
+ }
3524
+ }
3525
+ },
3526
+ {
3527
+ name: "powerpoint_export_pdf",
3528
+ description: "Export/convert a PowerPoint presentation to PDF.",
3529
+ category: "utility",
3530
+ parameters: {
3531
+ type: "object",
3532
+ properties: {
3533
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
3534
+ path: { type: "string", description: "File path" },
3535
+ driveId: { type: "string", description: "Drive ID" }
3536
+ },
3537
+ required: []
3538
+ },
3539
+ async execute(_id, params) {
3540
+ try {
3541
+ const token = await tp.getAccessToken();
3542
+ const base = itemPath3(params);
3543
+ const res = await fetch(`https://graph.microsoft.com/v1.0${base}/content?format=pdf`, {
3544
+ headers: { Authorization: `Bearer ${token}` },
3545
+ redirect: "follow"
3546
+ });
3547
+ if (!res.ok) throw new Error(`Export failed: ${res.status}`);
3548
+ const buf = Buffer.from(await res.arrayBuffer());
3549
+ return jsonResult({
3550
+ format: "pdf",
3551
+ sizeBytes: buf.length,
3552
+ base64: buf.toString("base64"),
3553
+ note: "PDF content returned as base64. Save to file or send as attachment."
3554
+ });
3555
+ } catch (e) {
3556
+ return errorResult(e.message);
3557
+ }
3558
+ }
3559
+ },
3560
+ {
3561
+ name: "powerpoint_get_thumbnails",
3562
+ description: "Get thumbnail images of slides in a presentation.",
3563
+ category: "utility",
3564
+ parameters: {
3565
+ type: "object",
3566
+ properties: {
3567
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
3568
+ path: { type: "string", description: "File path" },
3569
+ driveId: { type: "string", description: "Drive ID" },
3570
+ size: { type: "string", description: "Thumbnail size: small, medium, large (default: medium)" }
3571
+ },
3572
+ required: []
3573
+ },
3574
+ async execute(_id, params) {
3575
+ try {
3576
+ const token = await tp.getAccessToken();
3577
+ const base = itemPath3(params);
3578
+ const data = await graph(token, `${base}/thumbnails`);
3579
+ const size = params.size || "medium";
3580
+ const thumbs = (data.value || []).map((set, i) => {
3581
+ const t = set[size] || set.medium || set.large || set.small;
3582
+ return { slide: i + 1, url: t?.url, width: t?.width, height: t?.height };
3583
+ }).filter((t) => t.url);
3584
+ return jsonResult({ thumbnails: thumbs, count: thumbs.length });
3585
+ } catch (e) {
3586
+ return errorResult(e.message);
3587
+ }
3588
+ }
3589
+ },
3590
+ {
3591
+ name: "powerpoint_create_from_template",
3592
+ description: "Create a new PowerPoint by copying a template file. Returns the new file for editing.",
3593
+ category: "utility",
3594
+ parameters: {
3595
+ type: "object",
3596
+ properties: {
3597
+ templateItemId: { type: "string", description: "Item ID of the template .pptx" },
3598
+ templatePath: { type: "string", description: "Path of the template (alternative to itemId)" },
3599
+ newName: { type: "string", description: 'Name for the new file (e.g., "Q1 Report.pptx")' },
3600
+ destFolder: { type: "string", description: "Destination folder path (default: same as template)" }
3601
+ },
3602
+ required: ["newName"]
3603
+ },
3604
+ async execute(_id, params) {
3605
+ try {
3606
+ const token = await tp.getAccessToken();
3607
+ const base = params.templateItemId ? `/me/drive/items/${params.templateItemId}` : `/me/drive/root:${params.templatePath}:`;
3608
+ const parentRef = {};
3609
+ if (params.destFolder) parentRef.path = `/drive/root:${params.destFolder}`;
3610
+ const copy = await graph(token, `${base}/copy`, {
3611
+ method: "POST",
3612
+ body: { name: params.newName, ...params.destFolder ? { parentReference: parentRef } : {} }
3613
+ });
3614
+ return jsonResult({
3615
+ status: "copy_initiated",
3616
+ monitorUrl: copy["@odata.context"] || null,
3617
+ name: params.newName,
3618
+ note: "Copy is async. File will appear in OneDrive shortly."
3619
+ });
3620
+ } catch (e) {
3621
+ return errorResult(e.message);
3622
+ }
3623
+ }
3624
+ },
3625
+ {
3626
+ name: "powerpoint_get_embed_url",
3627
+ description: "Get an embeddable preview URL for a PowerPoint presentation.",
3628
+ category: "utility",
3629
+ parameters: {
3630
+ type: "object",
3631
+ properties: {
3632
+ itemId: { type: "string", description: "OneDrive/SharePoint item ID" },
3633
+ path: { type: "string", description: "File path" },
3634
+ driveId: { type: "string", description: "Drive ID" }
3635
+ },
3636
+ required: []
3637
+ },
3638
+ async execute(_id, params) {
3639
+ try {
3640
+ const token = await tp.getAccessToken();
3641
+ const base = itemPath3(params);
3642
+ const preview = await graph(token, `${base}/preview`, { method: "POST", body: {} });
3643
+ return jsonResult({
3644
+ embedUrl: preview.getUrl,
3645
+ editUrl: preview.postUrl
3646
+ });
3647
+ } catch (e) {
3648
+ return errorResult(e.message);
3649
+ }
3650
+ }
3651
+ }
3652
+ ];
3653
+ }
3654
+
3655
+ // src/agent-tools/tools/microsoft/planner.ts
3656
+ function createPlannerTools(config, _options) {
3657
+ const tp = config.tokenProvider;
3658
+ return [
3659
+ {
3660
+ name: "planner_list_plans",
3661
+ description: "List all Planner plans the agent has access to (via group membership).",
3662
+ category: "utility",
3663
+ parameters: {
3664
+ type: "object",
3665
+ properties: {
3666
+ groupId: { type: "string", description: "Microsoft 365 Group/Team ID to list plans for" }
3667
+ },
3668
+ required: ["groupId"]
3669
+ },
3670
+ async execute(_id, params) {
3671
+ try {
3672
+ const token = await tp.getAccessToken();
3673
+ const data = await graph(token, `/groups/${params.groupId}/planner/plans`);
3674
+ const plans = (data.value || []).map((p) => ({
3675
+ id: p.id,
3676
+ title: p.title,
3677
+ created: p.createdDateTime,
3678
+ owner: p.owner
3679
+ }));
3680
+ return jsonResult({ plans, count: plans.length });
3681
+ } catch (e) {
3682
+ return errorResult(e.message);
3683
+ }
3684
+ }
3685
+ },
3686
+ {
3687
+ name: "planner_list_buckets",
3688
+ description: "List buckets (columns) in a Planner plan.",
3689
+ category: "utility",
3690
+ parameters: {
3691
+ type: "object",
3692
+ properties: {
3693
+ planId: { type: "string", description: "Plan ID" }
3694
+ },
3695
+ required: ["planId"]
3696
+ },
3697
+ async execute(_id, params) {
3698
+ try {
3699
+ const token = await tp.getAccessToken();
3700
+ const data = await graph(token, `/planner/plans/${params.planId}/buckets`);
3701
+ const buckets = (data.value || []).map((b) => ({
3702
+ id: b.id,
3703
+ name: b.name,
3704
+ orderHint: b.orderHint,
3705
+ planId: b.planId
3706
+ }));
3707
+ return jsonResult({ buckets, count: buckets.length });
3708
+ } catch (e) {
3709
+ return errorResult(e.message);
3710
+ }
3711
+ }
3712
+ },
3713
+ {
3714
+ name: "planner_list_tasks",
3715
+ description: "List tasks in a Planner plan or bucket.",
3716
+ category: "utility",
3717
+ parameters: {
3718
+ type: "object",
3719
+ properties: {
3720
+ planId: { type: "string", description: "Plan ID (lists all tasks in plan)" },
3721
+ bucketId: { type: "string", description: "Bucket ID (lists tasks in specific bucket)" }
3722
+ },
3723
+ required: []
3724
+ },
3725
+ async execute(_id, params) {
3726
+ try {
3727
+ const token = await tp.getAccessToken();
3728
+ const path = params.bucketId ? `/planner/buckets/${params.bucketId}/tasks` : `/planner/plans/${params.planId}/tasks`;
3729
+ if (!params.planId && !params.bucketId) throw new Error("Provide planId or bucketId");
3730
+ const data = await graph(token, path);
3731
+ const tasks = (data.value || []).map((t) => ({
3732
+ id: t.id,
3733
+ title: t.title,
3734
+ bucketId: t.bucketId,
3735
+ percentComplete: t.percentComplete,
3736
+ priority: t.priority,
3737
+ // 0-10, lower = higher
3738
+ assignees: t.assignments ? Object.keys(t.assignments) : [],
3739
+ dueDate: t.dueDateTime,
3740
+ created: t.createdDateTime,
3741
+ startDate: t.startDateTime,
3742
+ hasDescription: t.hasDescription
3743
+ }));
3744
+ return jsonResult({ tasks, count: tasks.length });
3745
+ } catch (e) {
3746
+ return errorResult(e.message);
3747
+ }
3748
+ }
3749
+ },
3750
+ {
3751
+ name: "planner_create_task",
3752
+ description: "Create a new task in a Planner plan.",
3753
+ category: "utility",
3754
+ parameters: {
3755
+ type: "object",
3756
+ properties: {
3757
+ planId: { type: "string", description: "Plan ID" },
3758
+ title: { type: "string", description: "Task title" },
3759
+ bucketId: { type: "string", description: "Bucket ID to place the task in" },
3760
+ assignees: { type: "string", description: "Comma-separated user IDs to assign" },
3761
+ dueDate: { type: "string", description: "Due date (ISO 8601)" },
3762
+ startDate: { type: "string", description: "Start date (ISO 8601)" },
3763
+ priority: { type: "number", description: "Priority: 1 (urgent), 3 (important), 5 (medium), 9 (low)" },
3764
+ percentComplete: { type: "number", description: "0, 50, or 100" }
3765
+ },
3766
+ required: ["planId", "title"]
3767
+ },
3768
+ async execute(_id, params) {
3769
+ try {
3770
+ const token = await tp.getAccessToken();
3771
+ const task = { planId: params.planId, title: params.title };
3772
+ if (params.bucketId) task.bucketId = params.bucketId;
3773
+ if (params.dueDate) task.dueDateTime = params.dueDate;
3774
+ if (params.startDate) task.startDateTime = params.startDate;
3775
+ if (params.priority !== void 0) task.priority = params.priority;
3776
+ if (params.percentComplete !== void 0) task.percentComplete = params.percentComplete;
3777
+ if (params.assignees) {
3778
+ task.assignments = {};
3779
+ params.assignees.split(",").forEach((id) => {
3780
+ task.assignments[id.trim()] = { "@odata.type": "#microsoft.graph.plannerAssignment", orderHint: " !" };
3781
+ });
3782
+ }
3783
+ const created = await graph(token, "/planner/tasks", { method: "POST", body: task });
3784
+ return jsonResult({ id: created.id, title: created.title, created: true });
3785
+ } catch (e) {
3786
+ return errorResult(e.message);
3787
+ }
3788
+ }
3789
+ },
3790
+ {
3791
+ name: "planner_update_task",
3792
+ description: "Update a Planner task (title, progress, bucket, priority, assignments).",
3793
+ category: "utility",
3794
+ parameters: {
3795
+ type: "object",
3796
+ properties: {
3797
+ taskId: { type: "string", description: "Task ID" },
3798
+ title: { type: "string", description: "New title" },
3799
+ percentComplete: { type: "number", description: "0 (not started), 50 (in progress), 100 (completed)" },
3800
+ bucketId: { type: "string", description: "Move to different bucket" },
3801
+ priority: { type: "number", description: "1 (urgent), 3 (important), 5 (medium), 9 (low)" },
3802
+ dueDate: { type: "string", description: "Due date (ISO 8601)" }
3803
+ },
3804
+ required: ["taskId"]
3805
+ },
3806
+ async execute(_id, params) {
3807
+ try {
3808
+ const token = await tp.getAccessToken();
3809
+ const existing = await graph(token, `/planner/tasks/${params.taskId}`, {
3810
+ query: { "$select": "id" }
3811
+ });
3812
+ const etag = existing["@odata.etag"];
3813
+ const update = {};
3814
+ if (params.title) update.title = params.title;
3815
+ if (params.percentComplete !== void 0) update.percentComplete = params.percentComplete;
3816
+ if (params.bucketId) update.bucketId = params.bucketId;
3817
+ if (params.priority !== void 0) update.priority = params.priority;
3818
+ if (params.dueDate) update.dueDateTime = params.dueDate;
3819
+ await graph(token, `/planner/tasks/${params.taskId}`, {
3820
+ method: "PATCH",
3821
+ body: update,
3822
+ headers: { "If-Match": etag }
3823
+ });
3824
+ return jsonResult({ updated: true, taskId: params.taskId });
3825
+ } catch (e) {
3826
+ return errorResult(e.message);
3827
+ }
3828
+ }
3829
+ },
3830
+ {
3831
+ name: "planner_delete_task",
3832
+ description: "Delete a Planner task.",
3833
+ category: "utility",
3834
+ parameters: {
3835
+ type: "object",
3836
+ properties: {
3837
+ taskId: { type: "string", description: "Task ID to delete" }
3838
+ },
3839
+ required: ["taskId"]
3840
+ },
3841
+ async execute(_id, params) {
3842
+ try {
3843
+ const token = await tp.getAccessToken();
3844
+ const existing = await graph(token, `/planner/tasks/${params.taskId}`, { query: { "$select": "id" } });
3845
+ await graph(token, `/planner/tasks/${params.taskId}`, {
3846
+ method: "DELETE",
3847
+ headers: { "If-Match": existing["@odata.etag"] }
3848
+ });
3849
+ return jsonResult({ deleted: true, taskId: params.taskId });
3850
+ } catch (e) {
3851
+ return errorResult(e.message);
3852
+ }
3853
+ }
3854
+ }
3855
+ ];
3856
+ }
3857
+
3858
+ // src/agent-tools/tools/microsoft/powerbi.ts
3859
+ var PBI_BASE = "https://api.powerbi.com/v1.0/myorg";
3860
+ async function pbi(token, path, opts) {
3861
+ const method = opts?.method || "GET";
3862
+ const url = new URL(PBI_BASE + path);
3863
+ if (opts?.query) for (const [k, v] of Object.entries(opts.query)) {
3864
+ if (v) url.searchParams.set(k, v);
3865
+ }
3866
+ const res = await fetch(url.toString(), {
3867
+ method,
3868
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
3869
+ body: opts?.body ? JSON.stringify(opts.body) : void 0
3870
+ });
3871
+ if (!res.ok) {
3872
+ const err = await res.text();
3873
+ throw new Error(`Power BI API ${res.status}: ${err}`);
3874
+ }
3875
+ if (res.status === 200 || res.status === 201) return res.json().catch(() => ({}));
3876
+ return {};
3877
+ }
3878
+ function createPowerBITools(config, _options) {
3879
+ const tp = config.tokenProvider;
3880
+ return [
3881
+ {
3882
+ name: "powerbi_list_workspaces",
3883
+ description: "List Power BI workspaces (groups) the agent has access to.",
3884
+ category: "utility",
3885
+ parameters: { type: "object", properties: {}, required: [] },
3886
+ async execute(_id) {
3887
+ try {
3888
+ const token = await tp.getAccessToken();
3889
+ const data = await pbi(token, "/groups");
3890
+ const workspaces = (data.value || []).map((w) => ({
3891
+ id: w.id,
3892
+ name: w.name,
3893
+ isOnDedicatedCapacity: w.isOnDedicatedCapacity,
3894
+ type: w.type
3895
+ }));
3896
+ return jsonResult({ workspaces, count: workspaces.length });
3897
+ } catch (e) {
3898
+ return errorResult(e.message);
3899
+ }
3900
+ }
3901
+ },
3902
+ {
3903
+ name: "powerbi_list_reports",
3904
+ description: "List reports in a Power BI workspace.",
3905
+ category: "utility",
3906
+ parameters: {
3907
+ type: "object",
3908
+ properties: {
3909
+ workspaceId: { type: "string", description: 'Workspace (group) ID. Omit for "My Workspace".' }
3910
+ },
3911
+ required: []
3912
+ },
3913
+ async execute(_id, params) {
3914
+ try {
3915
+ const token = await tp.getAccessToken();
3916
+ const path = params.workspaceId ? `/groups/${params.workspaceId}/reports` : "/reports";
3917
+ const data = await pbi(token, path);
3918
+ const reports = (data.value || []).map((r) => ({
3919
+ id: r.id,
3920
+ name: r.name,
3921
+ datasetId: r.datasetId,
3922
+ webUrl: r.webUrl,
3923
+ embedUrl: r.embedUrl,
3924
+ reportType: r.reportType
3925
+ }));
3926
+ return jsonResult({ reports, count: reports.length });
3927
+ } catch (e) {
3928
+ return errorResult(e.message);
3929
+ }
3930
+ }
3931
+ },
3932
+ {
3933
+ name: "powerbi_list_dashboards",
3934
+ description: "List dashboards in a Power BI workspace.",
3935
+ category: "utility",
3936
+ parameters: {
3937
+ type: "object",
3938
+ properties: {
3939
+ workspaceId: { type: "string", description: 'Workspace ID. Omit for "My Workspace".' }
3940
+ },
3941
+ required: []
3942
+ },
3943
+ async execute(_id, params) {
3944
+ try {
3945
+ const token = await tp.getAccessToken();
3946
+ const path = params.workspaceId ? `/groups/${params.workspaceId}/dashboards` : "/dashboards";
3947
+ const data = await pbi(token, path);
3948
+ const dashboards = (data.value || []).map((d) => ({
3949
+ id: d.id,
3950
+ displayName: d.displayName,
3951
+ webUrl: d.webUrl,
3952
+ embedUrl: d.embedUrl,
3953
+ isReadOnly: d.isReadOnly
3954
+ }));
3955
+ return jsonResult({ dashboards, count: dashboards.length });
3956
+ } catch (e) {
3957
+ return errorResult(e.message);
3958
+ }
3959
+ }
3960
+ },
3961
+ {
3962
+ name: "powerbi_list_datasets",
3963
+ description: "List datasets in a Power BI workspace.",
3964
+ category: "utility",
3965
+ parameters: {
3966
+ type: "object",
3967
+ properties: {
3968
+ workspaceId: { type: "string", description: 'Workspace ID. Omit for "My Workspace".' }
3969
+ },
3970
+ required: []
3971
+ },
3972
+ async execute(_id, params) {
3973
+ try {
3974
+ const token = await tp.getAccessToken();
3975
+ const path = params.workspaceId ? `/groups/${params.workspaceId}/datasets` : "/datasets";
3976
+ const data = await pbi(token, path);
3977
+ const datasets = (data.value || []).map((d) => ({
3978
+ id: d.id,
3979
+ name: d.name,
3980
+ configuredBy: d.configuredBy,
3981
+ isRefreshable: d.isRefreshable,
3982
+ isOnPremGatewayRequired: d.isOnPremGatewayRequired,
3983
+ webUrl: d.webUrl
3984
+ }));
3985
+ return jsonResult({ datasets, count: datasets.length });
3986
+ } catch (e) {
3987
+ return errorResult(e.message);
3988
+ }
3989
+ }
3990
+ },
3991
+ {
3992
+ name: "powerbi_refresh_dataset",
3993
+ description: "Trigger a data refresh on a Power BI dataset.",
3994
+ category: "utility",
3995
+ parameters: {
3996
+ type: "object",
3997
+ properties: {
3998
+ datasetId: { type: "string", description: "Dataset ID to refresh" },
3999
+ workspaceId: { type: "string", description: "Workspace ID (omit for My Workspace)" },
4000
+ notifyOption: { type: "string", description: "NoNotification, MailOnFailure, or MailOnCompletion (default: NoNotification)" }
4001
+ },
4002
+ required: ["datasetId"]
4003
+ },
4004
+ async execute(_id, params) {
4005
+ try {
4006
+ const token = await tp.getAccessToken();
4007
+ const path = params.workspaceId ? `/groups/${params.workspaceId}/datasets/${params.datasetId}/refreshes` : `/datasets/${params.datasetId}/refreshes`;
4008
+ await pbi(token, path, {
4009
+ method: "POST",
4010
+ body: { notifyOption: params.notifyOption || "NoNotification" }
4011
+ });
4012
+ return jsonResult({ refreshTriggered: true, datasetId: params.datasetId });
4013
+ } catch (e) {
4014
+ return errorResult(e.message);
4015
+ }
4016
+ }
4017
+ },
4018
+ {
4019
+ name: "powerbi_refresh_history",
4020
+ description: "Get refresh history for a dataset.",
4021
+ category: "utility",
4022
+ parameters: {
4023
+ type: "object",
4024
+ properties: {
4025
+ datasetId: { type: "string", description: "Dataset ID" },
4026
+ workspaceId: { type: "string", description: "Workspace ID" },
4027
+ maxResults: { type: "number", description: "Max results (default: 10)" }
4028
+ },
4029
+ required: ["datasetId"]
4030
+ },
4031
+ async execute(_id, params) {
4032
+ try {
4033
+ const token = await tp.getAccessToken();
4034
+ const path = params.workspaceId ? `/groups/${params.workspaceId}/datasets/${params.datasetId}/refreshes` : `/datasets/${params.datasetId}/refreshes`;
4035
+ const data = await pbi(token, path, {
4036
+ query: { "$top": String(params.maxResults || 10) }
4037
+ });
4038
+ const history = (data.value || []).map((r) => ({
4039
+ id: r.requestId,
4040
+ refreshType: r.refreshType,
4041
+ status: r.status,
4042
+ startTime: r.startTime,
4043
+ endTime: r.endTime,
4044
+ serviceExceptionJson: r.serviceExceptionJson
4045
+ }));
4046
+ return jsonResult({ refreshHistory: history, count: history.length });
4047
+ } catch (e) {
4048
+ return errorResult(e.message);
4049
+ }
4050
+ }
4051
+ },
4052
+ {
4053
+ name: "powerbi_execute_query",
4054
+ description: "Execute a DAX query against a Power BI dataset. Returns tabular results.",
4055
+ category: "utility",
4056
+ parameters: {
4057
+ type: "object",
4058
+ properties: {
4059
+ datasetId: { type: "string", description: "Dataset ID" },
4060
+ workspaceId: { type: "string", description: "Workspace ID" },
4061
+ query: { type: "string", description: `DAX query (e.g., "EVALUATE TOPN(10, 'Sales', 'Sales'[Amount], DESC)")` }
4062
+ },
4063
+ required: ["datasetId", "query"]
4064
+ },
4065
+ async execute(_id, params) {
4066
+ try {
4067
+ const token = await tp.getAccessToken();
4068
+ const path = params.workspaceId ? `/groups/${params.workspaceId}/datasets/${params.datasetId}/executeQueries` : `/datasets/${params.datasetId}/executeQueries`;
4069
+ const data = await pbi(token, path, {
4070
+ method: "POST",
4071
+ body: {
4072
+ queries: [{ query: params.query }],
4073
+ serializerSettings: { includeNulls: true }
4074
+ }
4075
+ });
4076
+ const result = data.results?.[0];
4077
+ const tables = result?.tables?.[0];
4078
+ return jsonResult({
4079
+ columns: tables?.columns?.map((c) => c.name),
4080
+ rows: tables?.rows,
4081
+ rowCount: tables?.rows?.length || 0
4082
+ });
4083
+ } catch (e) {
4084
+ return errorResult(e.message);
4085
+ }
4086
+ }
4087
+ },
4088
+ {
4089
+ name: "powerbi_dashboard_tiles",
4090
+ description: "List tiles (widgets) on a Power BI dashboard.",
4091
+ category: "utility",
4092
+ parameters: {
4093
+ type: "object",
4094
+ properties: {
4095
+ dashboardId: { type: "string", description: "Dashboard ID" },
4096
+ workspaceId: { type: "string", description: "Workspace ID" }
4097
+ },
4098
+ required: ["dashboardId"]
4099
+ },
4100
+ async execute(_id, params) {
4101
+ try {
4102
+ const token = await tp.getAccessToken();
4103
+ const path = params.workspaceId ? `/groups/${params.workspaceId}/dashboards/${params.dashboardId}/tiles` : `/dashboards/${params.dashboardId}/tiles`;
4104
+ const data = await pbi(token, path);
4105
+ const tiles = (data.value || []).map((t) => ({
4106
+ id: t.id,
4107
+ title: t.title,
4108
+ reportId: t.reportId,
4109
+ datasetId: t.datasetId,
4110
+ embedUrl: t.embedUrl,
4111
+ rowSpan: t.rowSpan,
4112
+ colSpan: t.colSpan
4113
+ }));
4114
+ return jsonResult({ tiles, count: tiles.length });
4115
+ } catch (e) {
4116
+ return errorResult(e.message);
4117
+ }
4118
+ }
4119
+ }
4120
+ ];
4121
+ }
4122
+
4123
+ // src/agent-tools/tools/microsoft/index.ts
4124
+ function createAllMicrosoftTools(config, options) {
4125
+ const enabled = options?.enabledMicrosoftServices;
4126
+ const has = (s) => enabled ? enabled.includes(s) : false;
4127
+ const core = !enabled;
4128
+ const tools = [];
4129
+ if (core || has("mail")) tools.push(...createOutlookMailTools(config, options));
4130
+ if (core || has("calendar")) tools.push(...createOutlookCalendarTools(config, options));
4131
+ if (core || has("onedrive")) tools.push(...createOneDriveTools(config, options));
4132
+ if (core || has("tasks")) tools.push(...createTodoTools(config, options));
4133
+ if (has("teams")) tools.push(...createTeamsTools(config, options));
4134
+ if (has("contacts")) tools.push(...createOutlookContactsTools(config, options));
4135
+ if (has("excel")) tools.push(...createExcelTools(config, options));
4136
+ if (has("excel-advanced")) tools.push(...createExcelAdvancedTools(config, options));
4137
+ if (has("sharepoint")) tools.push(...createSharePointTools(config, options));
4138
+ if (has("onenote")) tools.push(...createOneNoteTools(config, options));
4139
+ if (has("powerpoint")) tools.push(...createPowerPointTools(config, options));
4140
+ if (has("planner")) tools.push(...createPlannerTools(config, options));
4141
+ if (has("powerbi")) tools.push(...createPowerBITools(config, options));
4142
+ return tools;
4143
+ }
4144
+ export {
4145
+ createAllMicrosoftTools,
4146
+ createExcelAdvancedTools,
4147
+ createExcelTools,
4148
+ createOneDriveTools,
4149
+ createOneNoteTools,
4150
+ createOutlookCalendarTools,
4151
+ createOutlookContactsTools,
4152
+ createOutlookMailTools,
4153
+ createPlannerTools,
4154
+ createPowerBITools,
4155
+ createPowerPointTools,
4156
+ createSharePointTools,
4157
+ createTeamsTools,
4158
+ createTodoTools
4159
+ };