@devosurf/tesser-connectors 0.1.0-alpha.1 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/catalog/index.ts +13 -0
- package/index.ts +1 -0
- package/manifest.json +121 -0
- package/outlook-mail/index.ts +689 -0
- package/package.json +3 -3
- package/providers/microsoft.ts +18 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The Tesser connector library. **Apache-2.0** — connectors are permissive so the community and agents can contribute and embed them freely (ADR-0009). Never compile a connector into the AGPL server.
|
|
4
4
|
|
|
5
|
-
> Status: **implemented.** P0 set: `github`, `slack`, `http`, `resend`, `gmail`, `google-calendar
|
|
5
|
+
> Status: **implemented.** P0 set: `github`, `slack`, `http`, `resend`, `gmail`, `google-calendar`, plus the Microsoft Graph-backed `outlook-mail` mailbox connector. `pnpm codegen` assembles the registry (`index.ts`), the Catalog (`catalog/index.ts`) from provider declarations under `providers/`, and `manifest.json` — never hand-edit those three. Colocated `index.test.ts` runs against a fake fetch via `invokeAction`/`fakeFetch` from `@devosurf/tesser-testing`; `index.live.test.ts` suites hit real APIs (`pnpm test:live`, credentials from env, self-skipping).
|
|
6
6
|
|
|
7
7
|
A connector is **our own** typed artifact — no Nango, no third-party OAuth code (ADR-0004). One connector per directory (`connectors/<name>/index.ts` + colocated tests + `sampleData`); a codegen step owns the registry, so you never hand-edit a global file. Auth is declared **once** and injected into every Action as a pre-authed `ctx.http` client — the author **never names a token** (`ctx.auth` is a masked escape hatch for odd placements). Actions are nested typed calls that return *our* stable shape, which `output` validates — never the raw provider response (ADR-0012):
|
|
8
8
|
|
package/catalog/index.ts
CHANGED
|
@@ -40,6 +40,19 @@ export const catalog: Record<string, ProviderFacts> = {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
|
+
"microsoft": {
|
|
44
|
+
"id": "microsoft",
|
|
45
|
+
"displayName": "Microsoft",
|
|
46
|
+
"baseUrl": "https://graph.microsoft.com/v1.0",
|
|
47
|
+
"docsUrl": "https://learn.microsoft.com/graph",
|
|
48
|
+
"oauth2": {
|
|
49
|
+
"authorizeUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
50
|
+
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
|
51
|
+
"clientAuth": "body",
|
|
52
|
+
"scopeSeparator": " ",
|
|
53
|
+
"pkce": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
43
56
|
"resend": {
|
|
44
57
|
"id": "resend",
|
|
45
58
|
"displayName": "Resend",
|
package/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { default as googleDocs } from "./google-docs/index.js";
|
|
|
10
10
|
export { default as googleDrive } from "./google-drive/index.js";
|
|
11
11
|
export { default as googleSheets } from "./google-sheets/index.js";
|
|
12
12
|
export { default as http } from "./http/index.js";
|
|
13
|
+
export { default as outlookMail } from "./outlook-mail/index.js";
|
|
13
14
|
export { default as pi } from "./pi/index.js";
|
|
14
15
|
export { default as resend } from "./resend/index.js";
|
|
15
16
|
export { default as slack } from "./slack/index.js";
|
package/manifest.json
CHANGED
|
@@ -599,6 +599,127 @@
|
|
|
599
599
|
"triggers": [],
|
|
600
600
|
"describe": "Plain HTTP requests to any endpoint"
|
|
601
601
|
},
|
|
602
|
+
{
|
|
603
|
+
"id": "outlook-mail",
|
|
604
|
+
"auth": {
|
|
605
|
+
"default": {
|
|
606
|
+
"kind": "oauth2",
|
|
607
|
+
"scopes": [
|
|
608
|
+
"offline_access",
|
|
609
|
+
"Mail.Read",
|
|
610
|
+
"Mail.ReadWrite",
|
|
611
|
+
"Mail.Send"
|
|
612
|
+
],
|
|
613
|
+
"flow": "auth_code",
|
|
614
|
+
"provider": "microsoft"
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
"actions": [
|
|
618
|
+
{
|
|
619
|
+
"path": "messages.list",
|
|
620
|
+
"safety": "read",
|
|
621
|
+
"retrySafe": true,
|
|
622
|
+
"describe": "List messages in a folder using Microsoft Graph's stable mapped shape"
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
"path": "messages.get",
|
|
626
|
+
"safety": "read",
|
|
627
|
+
"retrySafe": true,
|
|
628
|
+
"describe": "Fetch one message with body, recipients, categories, and thread ids"
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
"path": "messages.send",
|
|
632
|
+
"safety": "write",
|
|
633
|
+
"retrySafe": false,
|
|
634
|
+
"describe": "Send a new email using Outlook Mail (not idempotent; creates Sent Items mail)"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
"path": "messages.reply",
|
|
638
|
+
"safety": "write",
|
|
639
|
+
"retrySafe": false,
|
|
640
|
+
"describe": "Send a reply to an existing message immediately (prefer drafts.createReply for human review)"
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
"path": "messages.markRead",
|
|
644
|
+
"safety": "write",
|
|
645
|
+
"retrySafe": true,
|
|
646
|
+
"describe": "Mark a message read (sets isRead=true)"
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
"path": "messages.move",
|
|
650
|
+
"safety": "write",
|
|
651
|
+
"retrySafe": false,
|
|
652
|
+
"describe": "Move a message to another folder or well-known folder name (archive, deleteditems, junkemail, etc.)"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
"path": "messages.attachments.list",
|
|
656
|
+
"safety": "read",
|
|
657
|
+
"retrySafe": true,
|
|
658
|
+
"describe": "List a message's attachments"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
"path": "messages.attachments.get",
|
|
662
|
+
"safety": "read",
|
|
663
|
+
"retrySafe": true,
|
|
664
|
+
"describe": "Fetch one file attachment including base64 contentBytes when Graph returns them"
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
"path": "drafts.create",
|
|
668
|
+
"safety": "write",
|
|
669
|
+
"retrySafe": false,
|
|
670
|
+
"describe": "Create a new draft message for human review"
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
"path": "drafts.createReply",
|
|
674
|
+
"safety": "write",
|
|
675
|
+
"retrySafe": false,
|
|
676
|
+
"describe": "Create a reply draft for an existing message; send later with drafts.send"
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
"path": "drafts.update",
|
|
680
|
+
"safety": "write",
|
|
681
|
+
"retrySafe": true,
|
|
682
|
+
"describe": "Update a draft message's subject/body/recipients/categories"
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
"path": "drafts.send",
|
|
686
|
+
"safety": "write",
|
|
687
|
+
"retrySafe": false,
|
|
688
|
+
"describe": "Send an existing draft message (not idempotent)"
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
"path": "mailFolders.list",
|
|
692
|
+
"safety": "read",
|
|
693
|
+
"retrySafe": true,
|
|
694
|
+
"describe": "List root mail folders (well-known folders like inbox, archive, deleteditems can be used directly)"
|
|
695
|
+
}
|
|
696
|
+
],
|
|
697
|
+
"triggers": [
|
|
698
|
+
{
|
|
699
|
+
"id": "messageReceived",
|
|
700
|
+
"strategy": "poll",
|
|
701
|
+
"intervalDefault": "1m",
|
|
702
|
+
"intervalFloor": "30s",
|
|
703
|
+
"describe": "Fires for each new message created in an Outlook mail folder (delta-query poll)"
|
|
704
|
+
}
|
|
705
|
+
],
|
|
706
|
+
"describe": "Outlook Mail via Microsoft Graph — read, draft, send, label, and organize mail as the connected account",
|
|
707
|
+
"provider": "microsoft",
|
|
708
|
+
"providerFacts": {
|
|
709
|
+
"id": "microsoft",
|
|
710
|
+
"displayName": "Microsoft",
|
|
711
|
+
"baseUrl": "https://graph.microsoft.com/v1.0",
|
|
712
|
+
"docsUrl": "https://learn.microsoft.com/graph",
|
|
713
|
+
"oauth2": {
|
|
714
|
+
"authorizeUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
715
|
+
"tokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
|
716
|
+
"clientAuth": "body",
|
|
717
|
+
"scopeSeparator": " ",
|
|
718
|
+
"pkce": true
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
"baseUrl": "https://graph.microsoft.com/v1.0"
|
|
722
|
+
},
|
|
602
723
|
{
|
|
603
724
|
"id": "pi",
|
|
604
725
|
"auth": {
|
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
// Outlook Mail connector — Microsoft Graph mail surface for support-agent style
|
|
2
|
+
// inbox triage. Microsoft Graph sits behind the shared `microsoft` Provider so
|
|
3
|
+
// Teams/Excel/OneDrive can reuse the same OAuth app and token family later.
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { RetryableError, TerminalError } from "@devosurf/tesser-sdk";
|
|
7
|
+
import { action, defineConnector, oauth2, trigger, type ActionCtx } from "@devosurf/tesser-sdk/connector";
|
|
8
|
+
import { microsoftProvider } from "../providers/microsoft.js";
|
|
9
|
+
|
|
10
|
+
const SUMMARY_SELECT = [
|
|
11
|
+
"id",
|
|
12
|
+
"conversationId",
|
|
13
|
+
"subject",
|
|
14
|
+
"bodyPreview",
|
|
15
|
+
"receivedDateTime",
|
|
16
|
+
"webLink",
|
|
17
|
+
"isRead",
|
|
18
|
+
"hasAttachments",
|
|
19
|
+
"importance",
|
|
20
|
+
"categories",
|
|
21
|
+
"from",
|
|
22
|
+
"sender",
|
|
23
|
+
"parentFolderId",
|
|
24
|
+
].join(",");
|
|
25
|
+
|
|
26
|
+
const DETAIL_SELECT = [
|
|
27
|
+
SUMMARY_SELECT,
|
|
28
|
+
"body",
|
|
29
|
+
"toRecipients",
|
|
30
|
+
"ccRecipients",
|
|
31
|
+
"bccRecipients",
|
|
32
|
+
"replyTo",
|
|
33
|
+
"internetMessageId",
|
|
34
|
+
"isDraft",
|
|
35
|
+
].join(",");
|
|
36
|
+
|
|
37
|
+
const addressShape = z.object({ name: z.string(), address: z.string() });
|
|
38
|
+
const messageSummaryShape = z.object({
|
|
39
|
+
id: z.string(),
|
|
40
|
+
conversationId: z.string(),
|
|
41
|
+
parentFolderId: z.string(),
|
|
42
|
+
from: z.string(),
|
|
43
|
+
fromName: z.string(),
|
|
44
|
+
subject: z.string(),
|
|
45
|
+
bodyPreview: z.string(),
|
|
46
|
+
receivedAt: z.string(),
|
|
47
|
+
webLink: z.string().optional(),
|
|
48
|
+
isRead: z.boolean(),
|
|
49
|
+
hasAttachments: z.boolean(),
|
|
50
|
+
categories: z.array(z.string()),
|
|
51
|
+
importance: z.string(),
|
|
52
|
+
});
|
|
53
|
+
const messageDetailShape = messageSummaryShape.extend({
|
|
54
|
+
body: z.string(),
|
|
55
|
+
bodyContentType: z.enum(["text", "html", "unknown"]),
|
|
56
|
+
to: z.array(addressShape),
|
|
57
|
+
cc: z.array(addressShape),
|
|
58
|
+
bcc: z.array(addressShape),
|
|
59
|
+
replyTo: z.array(addressShape),
|
|
60
|
+
internetMessageId: z.string().optional(),
|
|
61
|
+
isDraft: z.boolean(),
|
|
62
|
+
});
|
|
63
|
+
const folderShape = z.object({
|
|
64
|
+
id: z.string(),
|
|
65
|
+
displayName: z.string(),
|
|
66
|
+
parentFolderId: z.string(),
|
|
67
|
+
childFolderCount: z.number(),
|
|
68
|
+
unreadItemCount: z.number(),
|
|
69
|
+
totalItemCount: z.number(),
|
|
70
|
+
isHidden: z.boolean(),
|
|
71
|
+
});
|
|
72
|
+
const attachmentShape = z.object({
|
|
73
|
+
id: z.string(),
|
|
74
|
+
name: z.string(),
|
|
75
|
+
contentType: z.string(),
|
|
76
|
+
size: z.number(),
|
|
77
|
+
isInline: z.boolean(),
|
|
78
|
+
});
|
|
79
|
+
const attachmentDetailShape = attachmentShape.extend({ contentBytes: z.string() });
|
|
80
|
+
|
|
81
|
+
const recipientsInput = z.union([z.string().min(3), z.array(z.string().min(3)).min(1)]);
|
|
82
|
+
const bodyContentTypeInput = z.enum(["text", "html"]).default("text");
|
|
83
|
+
|
|
84
|
+
const sendOrDraftInput = z.object({
|
|
85
|
+
to: recipientsInput,
|
|
86
|
+
subject: z.string(),
|
|
87
|
+
text: z.string(),
|
|
88
|
+
html: z.boolean().optional(),
|
|
89
|
+
cc: recipientsInput.optional(),
|
|
90
|
+
bcc: recipientsInput.optional(),
|
|
91
|
+
replyTo: recipientsInput.optional(),
|
|
92
|
+
categories: z.array(z.string()).optional(),
|
|
93
|
+
importance: z.enum(["low", "normal", "high"]).optional(),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const updateDraftInput = z.object({
|
|
97
|
+
id: z.string().min(1),
|
|
98
|
+
to: recipientsInput.optional(),
|
|
99
|
+
subject: z.string().optional(),
|
|
100
|
+
text: z.string().optional(),
|
|
101
|
+
html: z.boolean().optional(),
|
|
102
|
+
cc: recipientsInput.optional(),
|
|
103
|
+
bcc: recipientsInput.optional(),
|
|
104
|
+
replyTo: recipientsInput.optional(),
|
|
105
|
+
categories: z.array(z.string()).optional(),
|
|
106
|
+
importance: z.enum(["low", "normal", "high"]).optional(),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
type RawEmailAddress = { name?: string; address?: string };
|
|
110
|
+
type RawRecipient = { emailAddress?: RawEmailAddress };
|
|
111
|
+
type RawMessage = {
|
|
112
|
+
id: string;
|
|
113
|
+
conversationId?: string;
|
|
114
|
+
parentFolderId?: string;
|
|
115
|
+
from?: RawRecipient;
|
|
116
|
+
sender?: RawRecipient;
|
|
117
|
+
subject?: string;
|
|
118
|
+
bodyPreview?: string;
|
|
119
|
+
receivedDateTime?: string;
|
|
120
|
+
webLink?: string;
|
|
121
|
+
isRead?: boolean;
|
|
122
|
+
hasAttachments?: boolean;
|
|
123
|
+
categories?: string[];
|
|
124
|
+
importance?: string;
|
|
125
|
+
body?: { contentType?: string; content?: string };
|
|
126
|
+
toRecipients?: RawRecipient[];
|
|
127
|
+
ccRecipients?: RawRecipient[];
|
|
128
|
+
bccRecipients?: RawRecipient[];
|
|
129
|
+
replyTo?: RawRecipient[];
|
|
130
|
+
internetMessageId?: string;
|
|
131
|
+
isDraft?: boolean;
|
|
132
|
+
"@removed"?: { reason?: string };
|
|
133
|
+
};
|
|
134
|
+
type RawFolder = {
|
|
135
|
+
id: string;
|
|
136
|
+
displayName?: string;
|
|
137
|
+
parentFolderId?: string;
|
|
138
|
+
childFolderCount?: number;
|
|
139
|
+
unreadItemCount?: number;
|
|
140
|
+
totalItemCount?: number;
|
|
141
|
+
isHidden?: boolean;
|
|
142
|
+
};
|
|
143
|
+
type RawAttachment = {
|
|
144
|
+
id: string;
|
|
145
|
+
name?: string;
|
|
146
|
+
contentType?: string;
|
|
147
|
+
size?: number;
|
|
148
|
+
isInline?: boolean;
|
|
149
|
+
contentBytes?: string;
|
|
150
|
+
};
|
|
151
|
+
type GraphCollection<T> = {
|
|
152
|
+
value?: T[];
|
|
153
|
+
"@odata.nextLink"?: string;
|
|
154
|
+
"@odata.deltaLink"?: string;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
function address(raw: RawRecipient | undefined): z.infer<typeof addressShape> {
|
|
158
|
+
return {
|
|
159
|
+
name: raw?.emailAddress?.name ?? "",
|
|
160
|
+
address: raw?.emailAddress?.address ?? "",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function addresses(raw: RawRecipient[] | undefined): Array<z.infer<typeof addressShape>> {
|
|
165
|
+
return (raw ?? []).map(address);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function mapSummary(raw: RawMessage): z.infer<typeof messageSummaryShape> {
|
|
169
|
+
const from = address(raw.from ?? raw.sender);
|
|
170
|
+
return {
|
|
171
|
+
id: raw.id,
|
|
172
|
+
conversationId: raw.conversationId ?? "",
|
|
173
|
+
parentFolderId: raw.parentFolderId ?? "",
|
|
174
|
+
from: from.address,
|
|
175
|
+
fromName: from.name,
|
|
176
|
+
subject: raw.subject ?? "",
|
|
177
|
+
bodyPreview: raw.bodyPreview ?? "",
|
|
178
|
+
receivedAt: raw.receivedDateTime ?? "",
|
|
179
|
+
...(raw.webLink !== undefined ? { webLink: raw.webLink } : {}),
|
|
180
|
+
isRead: raw.isRead ?? false,
|
|
181
|
+
hasAttachments: raw.hasAttachments ?? false,
|
|
182
|
+
categories: raw.categories ?? [],
|
|
183
|
+
importance: raw.importance ?? "normal",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function mapDetail(raw: RawMessage): z.infer<typeof messageDetailShape> {
|
|
188
|
+
const contentType = (raw.body?.contentType ?? "unknown").toLowerCase();
|
|
189
|
+
return {
|
|
190
|
+
...mapSummary(raw),
|
|
191
|
+
body: raw.body?.content ?? "",
|
|
192
|
+
bodyContentType: contentType === "text" || contentType === "html" ? contentType : "unknown",
|
|
193
|
+
to: addresses(raw.toRecipients),
|
|
194
|
+
cc: addresses(raw.ccRecipients),
|
|
195
|
+
bcc: addresses(raw.bccRecipients),
|
|
196
|
+
replyTo: addresses(raw.replyTo),
|
|
197
|
+
...(raw.internetMessageId !== undefined ? { internetMessageId: raw.internetMessageId } : {}),
|
|
198
|
+
isDraft: raw.isDraft ?? false,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function mapFolder(raw: RawFolder): z.infer<typeof folderShape> {
|
|
203
|
+
return {
|
|
204
|
+
id: raw.id,
|
|
205
|
+
displayName: raw.displayName ?? "",
|
|
206
|
+
parentFolderId: raw.parentFolderId ?? "",
|
|
207
|
+
childFolderCount: raw.childFolderCount ?? 0,
|
|
208
|
+
unreadItemCount: raw.unreadItemCount ?? 0,
|
|
209
|
+
totalItemCount: raw.totalItemCount ?? 0,
|
|
210
|
+
isHidden: raw.isHidden ?? false,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function mapAttachment(raw: RawAttachment): z.infer<typeof attachmentShape> {
|
|
215
|
+
return {
|
|
216
|
+
id: raw.id,
|
|
217
|
+
name: raw.name ?? "",
|
|
218
|
+
contentType: raw.contentType ?? "",
|
|
219
|
+
size: raw.size ?? 0,
|
|
220
|
+
isInline: raw.isInline ?? false,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function mapAttachmentDetail(raw: RawAttachment): z.infer<typeof attachmentDetailShape> {
|
|
225
|
+
return { ...mapAttachment(raw), contentBytes: raw.contentBytes ?? "" };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function recipientObjects(input: z.infer<typeof recipientsInput> | undefined): RawRecipient[] | undefined {
|
|
229
|
+
if (input === undefined) return undefined;
|
|
230
|
+
const values = Array.isArray(input) ? input : [input];
|
|
231
|
+
return values.map((address) => ({ emailAddress: { address } }));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function messagePayload(input: z.infer<typeof sendOrDraftInput>): Record<string, unknown> {
|
|
235
|
+
return {
|
|
236
|
+
subject: input.subject,
|
|
237
|
+
body: { contentType: input.html === true ? "HTML" : "Text", content: input.text },
|
|
238
|
+
toRecipients: recipientObjects(input.to),
|
|
239
|
+
...(input.cc !== undefined ? { ccRecipients: recipientObjects(input.cc) } : {}),
|
|
240
|
+
...(input.bcc !== undefined ? { bccRecipients: recipientObjects(input.bcc) } : {}),
|
|
241
|
+
...(input.replyTo !== undefined ? { replyTo: recipientObjects(input.replyTo) } : {}),
|
|
242
|
+
...(input.categories !== undefined ? { categories: input.categories } : {}),
|
|
243
|
+
...(input.importance !== undefined ? { importance: input.importance } : {}),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function draftPatch(input: z.infer<typeof updateDraftInput>): Record<string, unknown> {
|
|
248
|
+
return {
|
|
249
|
+
...(input.subject !== undefined ? { subject: input.subject } : {}),
|
|
250
|
+
...(input.text !== undefined ? { body: { contentType: input.html === true ? "HTML" : "Text", content: input.text } } : {}),
|
|
251
|
+
...(input.to !== undefined ? { toRecipients: recipientObjects(input.to) } : {}),
|
|
252
|
+
...(input.cc !== undefined ? { ccRecipients: recipientObjects(input.cc) } : {}),
|
|
253
|
+
...(input.bcc !== undefined ? { bccRecipients: recipientObjects(input.bcc) } : {}),
|
|
254
|
+
...(input.replyTo !== undefined ? { replyTo: recipientObjects(input.replyTo) } : {}),
|
|
255
|
+
...(input.categories !== undefined ? { categories: input.categories } : {}),
|
|
256
|
+
...(input.importance !== undefined ? { importance: input.importance } : {}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function messagePath(id: string): string {
|
|
261
|
+
return `/me/messages/${encodeURIComponent(id)}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function folderMessagesPath(folderId: string): string {
|
|
265
|
+
return `/me/mailFolders/${encodeURIComponent(folderId)}/messages`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function listCollection<T>(
|
|
269
|
+
ctx: ActionCtx,
|
|
270
|
+
firstPath: string,
|
|
271
|
+
query: Record<string, string | number | boolean | undefined>,
|
|
272
|
+
limit: number,
|
|
273
|
+
): Promise<T[]> {
|
|
274
|
+
const out: T[] = [];
|
|
275
|
+
let path = firstPath;
|
|
276
|
+
for (let page = 0; page < 20 && out.length < limit; page++) {
|
|
277
|
+
const res = (await ctx.http.get(path, page === 0 ? { query } : undefined)) as GraphCollection<T>;
|
|
278
|
+
out.push(...(res.value ?? []));
|
|
279
|
+
if (!res["@odata.nextLink"]) return out.slice(0, limit);
|
|
280
|
+
path = res["@odata.nextLink"];
|
|
281
|
+
}
|
|
282
|
+
if (out.length >= limit) return out.slice(0, limit);
|
|
283
|
+
throw new RetryableError(`microsoft graph pagination exceeded safety cap for ${firstPath}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function pollCreatedMessages(
|
|
287
|
+
ctx: ActionCtx,
|
|
288
|
+
folderId: string,
|
|
289
|
+
maxPageSize: number,
|
|
290
|
+
cursor: unknown,
|
|
291
|
+
): Promise<{ items: RawMessage[]; nextCursor?: string }> {
|
|
292
|
+
const items: RawMessage[] = [];
|
|
293
|
+
let path = typeof cursor === "string" && cursor.length > 0 ? cursor : `${folderMessagesPath(folderId)}/delta`;
|
|
294
|
+
for (let page = 0; page < 20; page++) {
|
|
295
|
+
const first = page === 0 && !(typeof cursor === "string" && cursor.length > 0);
|
|
296
|
+
const res = (await ctx.http.get(path, {
|
|
297
|
+
...(first
|
|
298
|
+
? {
|
|
299
|
+
query: {
|
|
300
|
+
changeType: "created",
|
|
301
|
+
$select: SUMMARY_SELECT,
|
|
302
|
+
$orderby: "receivedDateTime desc",
|
|
303
|
+
$top: maxPageSize,
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
: {}),
|
|
307
|
+
headers: { Prefer: `odata.maxpagesize=${maxPageSize}` },
|
|
308
|
+
})) as GraphCollection<RawMessage>;
|
|
309
|
+
items.push(...(res.value ?? []).filter((m) => m["@removed"] === undefined));
|
|
310
|
+
if (res["@odata.deltaLink"] !== undefined) {
|
|
311
|
+
return { items, nextCursor: res["@odata.deltaLink"] };
|
|
312
|
+
}
|
|
313
|
+
if (res["@odata.nextLink"] === undefined) return { items };
|
|
314
|
+
path = res["@odata.nextLink"];
|
|
315
|
+
}
|
|
316
|
+
throw new RetryableError(`microsoft graph delta pagination exceeded safety cap for ${folderMessagesPath(folderId)}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function preferBody(contentType: "text" | "html", allowUnsafeHtml?: boolean): string {
|
|
320
|
+
const parts = [`outlook.body-content-type=\"${contentType}\"`];
|
|
321
|
+
if (allowUnsafeHtml === true) parts.push("outlook.allow-unsafe-html");
|
|
322
|
+
return parts.join(", ");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export default defineConnector({
|
|
326
|
+
id: "outlook-mail",
|
|
327
|
+
describe: "Outlook Mail via Microsoft Graph — read, draft, send, label, and organize mail as the connected account",
|
|
328
|
+
provider: microsoftProvider,
|
|
329
|
+
baseUrl: "https://graph.microsoft.com/v1.0",
|
|
330
|
+
auth: oauth2({
|
|
331
|
+
provider: "microsoft",
|
|
332
|
+
scopes: ["offline_access", "Mail.Read", "Mail.ReadWrite", "Mail.Send"],
|
|
333
|
+
}),
|
|
334
|
+
defaultHeaders: {
|
|
335
|
+
accept: "application/json",
|
|
336
|
+
},
|
|
337
|
+
samples: {
|
|
338
|
+
"messages.list": [
|
|
339
|
+
{
|
|
340
|
+
id: "AAMkAD1",
|
|
341
|
+
conversationId: "AAQkAD1",
|
|
342
|
+
parentFolderId: "inbox",
|
|
343
|
+
from: "ada@example.com",
|
|
344
|
+
fromName: "Ada",
|
|
345
|
+
subject: "sample subject",
|
|
346
|
+
bodyPreview: "hello",
|
|
347
|
+
receivedAt: "2026-01-01T00:00:00Z",
|
|
348
|
+
webLink: "https://outlook.office.com/mail/inbox/id/AAMkAD1",
|
|
349
|
+
isRead: false,
|
|
350
|
+
hasAttachments: false,
|
|
351
|
+
categories: [],
|
|
352
|
+
importance: "normal",
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
"messages.get": {
|
|
356
|
+
id: "AAMkAD1",
|
|
357
|
+
conversationId: "AAQkAD1",
|
|
358
|
+
parentFolderId: "inbox",
|
|
359
|
+
from: "ada@example.com",
|
|
360
|
+
fromName: "Ada",
|
|
361
|
+
subject: "sample subject",
|
|
362
|
+
bodyPreview: "hello",
|
|
363
|
+
receivedAt: "2026-01-01T00:00:00Z",
|
|
364
|
+
isRead: false,
|
|
365
|
+
hasAttachments: false,
|
|
366
|
+
categories: [],
|
|
367
|
+
importance: "normal",
|
|
368
|
+
body: "hello there",
|
|
369
|
+
bodyContentType: "text",
|
|
370
|
+
to: [{ name: "Support", address: "support@example.com" }],
|
|
371
|
+
cc: [],
|
|
372
|
+
bcc: [],
|
|
373
|
+
replyTo: [],
|
|
374
|
+
internetMessageId: "<m1@example.com>",
|
|
375
|
+
isDraft: false,
|
|
376
|
+
},
|
|
377
|
+
"messages.send": { accepted: true },
|
|
378
|
+
"messages.reply": { accepted: true },
|
|
379
|
+
"messages.markRead": {
|
|
380
|
+
id: "AAMkAD1",
|
|
381
|
+
conversationId: "AAQkAD1",
|
|
382
|
+
parentFolderId: "inbox",
|
|
383
|
+
from: "ada@example.com",
|
|
384
|
+
fromName: "Ada",
|
|
385
|
+
subject: "sample subject",
|
|
386
|
+
bodyPreview: "hello",
|
|
387
|
+
receivedAt: "2026-01-01T00:00:00Z",
|
|
388
|
+
isRead: true,
|
|
389
|
+
hasAttachments: false,
|
|
390
|
+
categories: [],
|
|
391
|
+
importance: "normal",
|
|
392
|
+
},
|
|
393
|
+
"messages.move": {
|
|
394
|
+
id: "AAMkAD2",
|
|
395
|
+
conversationId: "AAQkAD1",
|
|
396
|
+
parentFolderId: "archive",
|
|
397
|
+
from: "ada@example.com",
|
|
398
|
+
fromName: "Ada",
|
|
399
|
+
subject: "sample subject",
|
|
400
|
+
bodyPreview: "hello",
|
|
401
|
+
receivedAt: "2026-01-01T00:00:00Z",
|
|
402
|
+
isRead: true,
|
|
403
|
+
hasAttachments: false,
|
|
404
|
+
categories: [],
|
|
405
|
+
importance: "normal",
|
|
406
|
+
},
|
|
407
|
+
"messages.attachments.list": [
|
|
408
|
+
{ id: "att1", name: "invoice.pdf", contentType: "application/pdf", size: 1234, isInline: false },
|
|
409
|
+
],
|
|
410
|
+
"messages.attachments.get": {
|
|
411
|
+
id: "att1",
|
|
412
|
+
name: "invoice.pdf",
|
|
413
|
+
contentType: "application/pdf",
|
|
414
|
+
size: 1234,
|
|
415
|
+
isInline: false,
|
|
416
|
+
contentBytes: "JVBERi0=",
|
|
417
|
+
},
|
|
418
|
+
"drafts.create": {
|
|
419
|
+
id: "draft1",
|
|
420
|
+
conversationId: "conv1",
|
|
421
|
+
parentFolderId: "drafts",
|
|
422
|
+
from: "support@example.com",
|
|
423
|
+
fromName: "Support",
|
|
424
|
+
subject: "Re: hello",
|
|
425
|
+
bodyPreview: "reply",
|
|
426
|
+
receivedAt: "2026-01-01T00:00:00Z",
|
|
427
|
+
isRead: true,
|
|
428
|
+
hasAttachments: false,
|
|
429
|
+
categories: [],
|
|
430
|
+
importance: "normal",
|
|
431
|
+
body: "reply",
|
|
432
|
+
bodyContentType: "text",
|
|
433
|
+
to: [{ name: "Ada", address: "ada@example.com" }],
|
|
434
|
+
cc: [],
|
|
435
|
+
bcc: [],
|
|
436
|
+
replyTo: [],
|
|
437
|
+
isDraft: true,
|
|
438
|
+
},
|
|
439
|
+
"drafts.createReply": {
|
|
440
|
+
id: "replyDraft1",
|
|
441
|
+
conversationId: "conv1",
|
|
442
|
+
parentFolderId: "drafts",
|
|
443
|
+
from: "support@example.com",
|
|
444
|
+
fromName: "Support",
|
|
445
|
+
subject: "Re: hello",
|
|
446
|
+
bodyPreview: "reply",
|
|
447
|
+
receivedAt: "2026-01-01T00:00:00Z",
|
|
448
|
+
isRead: true,
|
|
449
|
+
hasAttachments: false,
|
|
450
|
+
categories: [],
|
|
451
|
+
importance: "normal",
|
|
452
|
+
body: "reply",
|
|
453
|
+
bodyContentType: "text",
|
|
454
|
+
to: [{ name: "Ada", address: "ada@example.com" }],
|
|
455
|
+
cc: [],
|
|
456
|
+
bcc: [],
|
|
457
|
+
replyTo: [],
|
|
458
|
+
isDraft: true,
|
|
459
|
+
},
|
|
460
|
+
"drafts.update": {
|
|
461
|
+
id: "draft1",
|
|
462
|
+
conversationId: "conv1",
|
|
463
|
+
parentFolderId: "drafts",
|
|
464
|
+
from: "support@example.com",
|
|
465
|
+
fromName: "Support",
|
|
466
|
+
subject: "Re: hello",
|
|
467
|
+
bodyPreview: "updated",
|
|
468
|
+
receivedAt: "2026-01-01T00:00:00Z",
|
|
469
|
+
isRead: true,
|
|
470
|
+
hasAttachments: false,
|
|
471
|
+
categories: [],
|
|
472
|
+
importance: "normal",
|
|
473
|
+
body: "updated",
|
|
474
|
+
bodyContentType: "text",
|
|
475
|
+
to: [{ name: "Ada", address: "ada@example.com" }],
|
|
476
|
+
cc: [],
|
|
477
|
+
bcc: [],
|
|
478
|
+
replyTo: [],
|
|
479
|
+
isDraft: true,
|
|
480
|
+
},
|
|
481
|
+
"drafts.send": { accepted: true },
|
|
482
|
+
"mailFolders.list": [
|
|
483
|
+
{
|
|
484
|
+
id: "inbox",
|
|
485
|
+
displayName: "Inbox",
|
|
486
|
+
parentFolderId: "root",
|
|
487
|
+
childFolderCount: 0,
|
|
488
|
+
unreadItemCount: 3,
|
|
489
|
+
totalItemCount: 10,
|
|
490
|
+
isHidden: false,
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
"trigger:messageReceived": {
|
|
494
|
+
id: "AAMkAD3",
|
|
495
|
+
conversationId: "AAQkAD3",
|
|
496
|
+
parentFolderId: "inbox",
|
|
497
|
+
from: "ada@example.com",
|
|
498
|
+
fromName: "Ada",
|
|
499
|
+
subject: "new mail",
|
|
500
|
+
bodyPreview: "hello",
|
|
501
|
+
receivedAt: "2026-01-01T00:00:00Z",
|
|
502
|
+
isRead: false,
|
|
503
|
+
hasAttachments: false,
|
|
504
|
+
categories: [],
|
|
505
|
+
importance: "normal",
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
actions: {
|
|
509
|
+
messages: {
|
|
510
|
+
list: action({
|
|
511
|
+
describe: "List messages in a folder using Microsoft Graph's stable mapped shape",
|
|
512
|
+
input: z.object({
|
|
513
|
+
folderId: z.string().default("inbox"),
|
|
514
|
+
filter: z.string().optional(),
|
|
515
|
+
maxResults: z.number().int().min(1).max(250).default(25),
|
|
516
|
+
}),
|
|
517
|
+
output: z.array(messageSummaryShape),
|
|
518
|
+
safety: "read",
|
|
519
|
+
run: async (ctx, i) => {
|
|
520
|
+
const raw = await listCollection<RawMessage>(
|
|
521
|
+
ctx,
|
|
522
|
+
folderMessagesPath(i.folderId),
|
|
523
|
+
{
|
|
524
|
+
$select: SUMMARY_SELECT,
|
|
525
|
+
$orderby: "receivedDateTime desc",
|
|
526
|
+
$top: Math.min(i.maxResults, 100),
|
|
527
|
+
...(i.filter !== undefined ? { $filter: i.filter } : {}),
|
|
528
|
+
},
|
|
529
|
+
i.maxResults,
|
|
530
|
+
);
|
|
531
|
+
return raw.map(mapSummary);
|
|
532
|
+
},
|
|
533
|
+
}),
|
|
534
|
+
get: action({
|
|
535
|
+
describe: "Fetch one message with body, recipients, categories, and thread ids",
|
|
536
|
+
input: z.object({
|
|
537
|
+
id: z.string().min(1),
|
|
538
|
+
bodyContentType: bodyContentTypeInput,
|
|
539
|
+
allowUnsafeHtml: z.boolean().optional(),
|
|
540
|
+
}),
|
|
541
|
+
output: messageDetailShape,
|
|
542
|
+
safety: "read",
|
|
543
|
+
run: async (ctx, i) => {
|
|
544
|
+
const raw = (await ctx.http.get(messagePath(i.id), {
|
|
545
|
+
query: { $select: DETAIL_SELECT },
|
|
546
|
+
headers: { Prefer: preferBody(i.bodyContentType, i.allowUnsafeHtml) },
|
|
547
|
+
})) as RawMessage;
|
|
548
|
+
return mapDetail(raw);
|
|
549
|
+
},
|
|
550
|
+
}),
|
|
551
|
+
send: action({
|
|
552
|
+
describe: "Send a new email using Outlook Mail (not idempotent; creates Sent Items mail)",
|
|
553
|
+
input: sendOrDraftInput,
|
|
554
|
+
output: z.object({ accepted: z.boolean() }),
|
|
555
|
+
run: async (ctx, i) => {
|
|
556
|
+
await ctx.http.post("/me/sendMail", { message: messagePayload(i) });
|
|
557
|
+
return { accepted: true };
|
|
558
|
+
},
|
|
559
|
+
}),
|
|
560
|
+
reply: action({
|
|
561
|
+
describe: "Send a reply to an existing message immediately (prefer drafts.createReply for human review)",
|
|
562
|
+
input: z.object({
|
|
563
|
+
id: z.string().min(1),
|
|
564
|
+
comment: z.string().optional(),
|
|
565
|
+
to: recipientsInput.optional(),
|
|
566
|
+
}),
|
|
567
|
+
output: z.object({ accepted: z.boolean() }),
|
|
568
|
+
run: async (ctx, i) => {
|
|
569
|
+
await ctx.http.post(`${messagePath(i.id)}/reply`, {
|
|
570
|
+
...(i.comment !== undefined ? { comment: i.comment } : {}),
|
|
571
|
+
...(i.to !== undefined ? { message: { toRecipients: recipientObjects(i.to) } } : {}),
|
|
572
|
+
});
|
|
573
|
+
return { accepted: true };
|
|
574
|
+
},
|
|
575
|
+
}),
|
|
576
|
+
markRead: action({
|
|
577
|
+
describe: "Mark a message read (sets isRead=true)",
|
|
578
|
+
input: z.object({ id: z.string().min(1) }),
|
|
579
|
+
output: messageSummaryShape,
|
|
580
|
+
retrySafe: true,
|
|
581
|
+
run: async (ctx, i) => mapSummary((await ctx.http.patch(messagePath(i.id), { isRead: true })) as RawMessage),
|
|
582
|
+
}),
|
|
583
|
+
move: action({
|
|
584
|
+
describe: "Move a message to another folder or well-known folder name (archive, deleteditems, junkemail, etc.)",
|
|
585
|
+
input: z.object({ id: z.string().min(1), destinationId: z.string().min(1) }),
|
|
586
|
+
output: messageSummaryShape,
|
|
587
|
+
run: async (ctx, i) =>
|
|
588
|
+
mapSummary((await ctx.http.post(`${messagePath(i.id)}/move`, { destinationId: i.destinationId })) as RawMessage),
|
|
589
|
+
}),
|
|
590
|
+
attachments: {
|
|
591
|
+
list: action({
|
|
592
|
+
describe: "List a message's attachments",
|
|
593
|
+
input: z.object({ messageId: z.string().min(1) }),
|
|
594
|
+
output: z.array(attachmentShape),
|
|
595
|
+
safety: "read",
|
|
596
|
+
run: async (ctx, i) => {
|
|
597
|
+
const raw = (await ctx.http.get(`${messagePath(i.messageId)}/attachments`)) as GraphCollection<RawAttachment>;
|
|
598
|
+
return (raw.value ?? []).map(mapAttachment);
|
|
599
|
+
},
|
|
600
|
+
}),
|
|
601
|
+
get: action({
|
|
602
|
+
describe: "Fetch one file attachment including base64 contentBytes when Graph returns them",
|
|
603
|
+
input: z.object({ messageId: z.string().min(1), id: z.string().min(1) }),
|
|
604
|
+
output: attachmentDetailShape,
|
|
605
|
+
safety: "read",
|
|
606
|
+
run: async (ctx, i) =>
|
|
607
|
+
mapAttachmentDetail(
|
|
608
|
+
(await ctx.http.get(`${messagePath(i.messageId)}/attachments/${encodeURIComponent(i.id)}`)) as RawAttachment,
|
|
609
|
+
),
|
|
610
|
+
}),
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
drafts: {
|
|
614
|
+
create: action({
|
|
615
|
+
describe: "Create a new draft message for human review",
|
|
616
|
+
input: sendOrDraftInput,
|
|
617
|
+
output: messageDetailShape,
|
|
618
|
+
run: async (ctx, i) => mapDetail((await ctx.http.post("/me/messages", messagePayload(i))) as RawMessage),
|
|
619
|
+
}),
|
|
620
|
+
createReply: action({
|
|
621
|
+
describe: "Create a reply draft for an existing message; send later with drafts.send",
|
|
622
|
+
input: z.object({
|
|
623
|
+
messageId: z.string().min(1),
|
|
624
|
+
comment: z.string().optional(),
|
|
625
|
+
text: z.string().optional(),
|
|
626
|
+
html: z.boolean().optional(),
|
|
627
|
+
}),
|
|
628
|
+
output: messageDetailShape,
|
|
629
|
+
run: async (ctx, i) => {
|
|
630
|
+
if (i.comment !== undefined && i.text !== undefined) {
|
|
631
|
+
throw new TerminalError("outlook-mail.drafts.createReply: pass either comment or text, not both");
|
|
632
|
+
}
|
|
633
|
+
const body =
|
|
634
|
+
i.text !== undefined
|
|
635
|
+
? { message: { body: { contentType: i.html === true ? "HTML" : "Text", content: i.text } } }
|
|
636
|
+
: i.comment !== undefined
|
|
637
|
+
? { comment: i.comment }
|
|
638
|
+
: undefined;
|
|
639
|
+
return mapDetail((await ctx.http.post(`${messagePath(i.messageId)}/createReply`, body)) as RawMessage);
|
|
640
|
+
},
|
|
641
|
+
}),
|
|
642
|
+
update: action({
|
|
643
|
+
describe: "Update a draft message's subject/body/recipients/categories",
|
|
644
|
+
input: updateDraftInput,
|
|
645
|
+
output: messageDetailShape,
|
|
646
|
+
retrySafe: true,
|
|
647
|
+
run: async (ctx, i) => mapDetail((await ctx.http.patch(messagePath(i.id), draftPatch(i))) as RawMessage),
|
|
648
|
+
}),
|
|
649
|
+
send: action({
|
|
650
|
+
describe: "Send an existing draft message (not idempotent)",
|
|
651
|
+
input: z.object({ id: z.string().min(1) }),
|
|
652
|
+
output: z.object({ accepted: z.boolean() }),
|
|
653
|
+
run: async (ctx, i) => {
|
|
654
|
+
await ctx.http.post(`${messagePath(i.id)}/send`);
|
|
655
|
+
return { accepted: true };
|
|
656
|
+
},
|
|
657
|
+
}),
|
|
658
|
+
},
|
|
659
|
+
mailFolders: {
|
|
660
|
+
list: action({
|
|
661
|
+
describe: "List root mail folders (well-known folders like inbox, archive, deleteditems can be used directly)",
|
|
662
|
+
input: z.object({ includeHiddenFolders: z.boolean().default(false), maxResults: z.number().int().min(1).max(250).default(100) }),
|
|
663
|
+
output: z.array(folderShape),
|
|
664
|
+
safety: "read",
|
|
665
|
+
run: async (ctx, i) => {
|
|
666
|
+
const raw = await listCollection<RawFolder>(
|
|
667
|
+
ctx,
|
|
668
|
+
"/me/mailFolders",
|
|
669
|
+
{ includeHiddenFolders: i.includeHiddenFolders, $top: Math.min(i.maxResults, 100) },
|
|
670
|
+
i.maxResults,
|
|
671
|
+
);
|
|
672
|
+
return raw.map(mapFolder);
|
|
673
|
+
},
|
|
674
|
+
}),
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
triggers: {
|
|
678
|
+
messageReceived: trigger.poll({
|
|
679
|
+
describe: "Fires for each new message created in an Outlook mail folder (delta-query poll)",
|
|
680
|
+
input: z.object({ folderId: z.string().default("inbox"), maxPageSize: z.number().int().min(1).max(100).default(25) }),
|
|
681
|
+
output: messageSummaryShape,
|
|
682
|
+
interval: { default: "1m", floor: "30s" },
|
|
683
|
+
order: "newest-first",
|
|
684
|
+
poll: (ctx, params, cursor) => pollCreatedMessages(ctx, params.folderId, params.maxPageSize, cursor),
|
|
685
|
+
dedupeKey: (raw) => (raw as RawMessage).id,
|
|
686
|
+
map: (raw) => mapSummary(raw as RawMessage),
|
|
687
|
+
}),
|
|
688
|
+
},
|
|
689
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devosurf/tesser-connectors",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"description": "Tesser connector library — typed integrations consumed as ctx.connections.<name>.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"zod": "^4.4.3",
|
|
17
|
-
"@devosurf/tesser-sdk": "0.1.0-alpha.
|
|
17
|
+
"@devosurf/tesser-sdk": "0.1.0-alpha.2"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@devosurf/tesser-testing": "^0.1.0-alpha.
|
|
20
|
+
"@devosurf/tesser-testing": "^0.1.0-alpha.2"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"index.ts",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ProviderFacts } from "@devosurf/tesser-sdk/connector";
|
|
2
|
+
|
|
3
|
+
// Shared Provider for Microsoft Graph surfaces (Outlook Mail, Teams, Excel,
|
|
4
|
+
// OneDrive/SharePoint). One Entra app backs the family; individual Connectors
|
|
5
|
+
// contribute their own Graph scopes.
|
|
6
|
+
export const microsoftProvider: ProviderFacts = {
|
|
7
|
+
id: "microsoft",
|
|
8
|
+
displayName: "Microsoft",
|
|
9
|
+
baseUrl: "https://graph.microsoft.com/v1.0",
|
|
10
|
+
docsUrl: "https://learn.microsoft.com/graph",
|
|
11
|
+
oauth2: {
|
|
12
|
+
authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
13
|
+
tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
|
14
|
+
clientAuth: "body",
|
|
15
|
+
scopeSeparator: " ",
|
|
16
|
+
pkce: true,
|
|
17
|
+
},
|
|
18
|
+
};
|