@slates-integrations/sharepoint 0.2.0-rc.6

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,195 @@
1
+ import { SlateTool } from 'slates';
2
+ import { SharePointClient } from '../lib/client';
3
+ import { spec } from '../spec';
4
+ import { z } from 'zod';
5
+
6
+ let getPermissionPrincipal = (permission: any) => {
7
+ let identities = [
8
+ permission.grantedToV2,
9
+ ...(permission.grantedToIdentitiesV2 || []),
10
+ permission.grantedTo,
11
+ ...(permission.grantedToIdentities || [])
12
+ ].filter(Boolean);
13
+
14
+ for (let identity of identities) {
15
+ let principal =
16
+ identity.user ||
17
+ identity.siteUser ||
18
+ identity.group ||
19
+ identity.siteGroup ||
20
+ identity.application ||
21
+ identity.device;
22
+ if (principal) {
23
+ return principal;
24
+ }
25
+ }
26
+
27
+ return null;
28
+ };
29
+
30
+ let permissionOutputSchema = z.object({
31
+ permissionId: z.string().describe('Permission ID'),
32
+ roles: z
33
+ .array(z.string())
34
+ .optional()
35
+ .describe('Permission roles (e.g. "read", "write", "owner")'),
36
+ grantedTo: z.string().optional().describe('User or group the permission is granted to'),
37
+ grantedToEmail: z
38
+ .string()
39
+ .optional()
40
+ .describe('Email of the user the permission is granted to'),
41
+ linkType: z.string().optional().describe('Type of sharing link (if applicable)'),
42
+ linkScope: z.string().optional().describe('Scope of sharing link'),
43
+ linkUrl: z.string().optional().describe('URL of the sharing link'),
44
+ expirationDateTime: z.string().optional().describe('When the permission expires')
45
+ });
46
+
47
+ export let managePermissions = SlateTool.create(spec, {
48
+ name: 'Manage Permissions',
49
+ key: 'manage_permissions',
50
+ description: `View, grant, and revoke permissions on SharePoint files and folders. Create sharing links, invite users with specific roles, list current permissions, or remove a permission. Works with drive items in document libraries.`,
51
+ instructions: [
52
+ 'Set **action** to "list" to see all current permissions on an item.',
53
+ 'Set **action** to "createLink" to generate a sharing link. Specify **linkType** (view/edit) and **linkScope** (anonymous/organization/users).',
54
+ 'Set **action** to "invite" to grant access to specific users by email with specified roles.',
55
+ 'Set **action** to "delete" to revoke a specific permission by its ID.'
56
+ ],
57
+ tags: {
58
+ destructive: false,
59
+ readOnly: false
60
+ }
61
+ })
62
+ .input(
63
+ z.object({
64
+ action: z
65
+ .enum(['list', 'createLink', 'invite', 'delete'])
66
+ .describe('Permission action to perform'),
67
+ driveId: z.string().describe('Drive ID containing the item'),
68
+ itemId: z.string().describe('Drive item ID to manage permissions for'),
69
+ linkType: z
70
+ .enum(['view', 'edit', 'embed'])
71
+ .optional()
72
+ .describe('Type of sharing link (for createLink)'),
73
+ linkScope: z
74
+ .enum(['anonymous', 'organization', 'users'])
75
+ .optional()
76
+ .describe('Scope of the sharing link (for createLink)'),
77
+ linkExpiration: z
78
+ .string()
79
+ .optional()
80
+ .describe('Expiration datetime in ISO 8601 format (for createLink)'),
81
+ linkPassword: z
82
+ .string()
83
+ .optional()
84
+ .describe('Password for the sharing link (for createLink)'),
85
+ recipientEmails: z
86
+ .array(z.string())
87
+ .optional()
88
+ .describe('Email addresses to invite (for invite)'),
89
+ roles: z
90
+ .array(z.enum(['read', 'write', 'owner']))
91
+ .optional()
92
+ .describe('Roles to assign to invitees (for invite)'),
93
+ inviteMessage: z
94
+ .string()
95
+ .optional()
96
+ .describe('Message to include in the invitation email (for invite)'),
97
+ sendInvitation: z
98
+ .boolean()
99
+ .optional()
100
+ .describe('Whether to send an email notification (for invite, default true)'),
101
+ permissionId: z
102
+ .string()
103
+ .optional()
104
+ .describe('Permission ID to delete (for delete action)')
105
+ })
106
+ )
107
+ .output(
108
+ z.object({
109
+ permissions: z.array(permissionOutputSchema).optional().describe('List of permissions'),
110
+ permission: permissionOutputSchema.optional().describe('Created or updated permission'),
111
+ deleted: z.boolean().optional().describe('Whether the permission was deleted')
112
+ })
113
+ )
114
+ .handleInvocation(async ctx => {
115
+ let client = new SharePointClient(ctx.auth.token);
116
+ let { action, driveId, itemId } = ctx.input;
117
+
118
+ let mapPermission = (p: any) => {
119
+ let principal = getPermissionPrincipal(p);
120
+ return {
121
+ permissionId: p.id,
122
+ roles: p.roles,
123
+ grantedTo:
124
+ principal?.displayName ||
125
+ principal?.loginName ||
126
+ principal?.email ||
127
+ principal?.userPrincipalName ||
128
+ principal?.id,
129
+ grantedToEmail: principal?.email || principal?.userPrincipalName,
130
+ linkType: p.link?.type,
131
+ linkScope: p.link?.scope,
132
+ linkUrl: p.link?.webUrl,
133
+ expirationDateTime: p.expirationDateTime
134
+ };
135
+ };
136
+
137
+ switch (action) {
138
+ case 'list': {
139
+ let data = await client.getDriveItemPermissions(driveId, itemId);
140
+ let permissions = (data.value || []).map(mapPermission);
141
+ return {
142
+ output: { permissions },
143
+ message: `Found **${permissions.length}** permission(s) on the item.`
144
+ };
145
+ }
146
+
147
+ case 'createLink': {
148
+ if (!ctx.input.linkType) throw new Error('linkType is required for createLink.');
149
+ if (!ctx.input.linkScope) throw new Error('linkScope is required for createLink.');
150
+ let perm = await client.createSharingLink(
151
+ driveId,
152
+ itemId,
153
+ ctx.input.linkType,
154
+ ctx.input.linkScope,
155
+ ctx.input.linkExpiration,
156
+ ctx.input.linkPassword
157
+ );
158
+ return {
159
+ output: { permission: mapPermission(perm) },
160
+ message: `Created **${ctx.input.linkType}** sharing link with **${ctx.input.linkScope}** scope.`
161
+ };
162
+ }
163
+
164
+ case 'invite': {
165
+ if (!ctx.input.recipientEmails || ctx.input.recipientEmails.length === 0) {
166
+ throw new Error('recipientEmails are required for invite.');
167
+ }
168
+ let roles = ctx.input.roles || ['read'];
169
+ let data = await client.inviteToItem(
170
+ driveId,
171
+ itemId,
172
+ ctx.input.recipientEmails.map(email => ({ email })),
173
+ roles,
174
+ ctx.input.inviteMessage,
175
+ true,
176
+ ctx.input.sendInvitation
177
+ );
178
+ let permissions = (data.value || []).map(mapPermission);
179
+ return {
180
+ output: { permissions },
181
+ message: `Invited **${ctx.input.recipientEmails.length}** user(s) with **${roles.join(', ')}** role(s).`
182
+ };
183
+ }
184
+
185
+ case 'delete': {
186
+ if (!ctx.input.permissionId) throw new Error('permissionId is required for delete.');
187
+ await client.deletePermission(driveId, itemId, ctx.input.permissionId);
188
+ return {
189
+ output: { deleted: true },
190
+ message: `Deleted permission \`${ctx.input.permissionId}\`.`
191
+ };
192
+ }
193
+ }
194
+ })
195
+ .build();
@@ -0,0 +1,61 @@
1
+ import { SlateTool } from 'slates';
2
+ import { SharePointClient } from '../lib/client';
3
+ import { spec } from '../spec';
4
+ import { z } from 'zod';
5
+
6
+ let searchResultSchema = z.object({
7
+ itemId: z.string().describe('Drive item ID'),
8
+ fileName: z.string().describe('File or folder name'),
9
+ webUrl: z.string().optional().describe('URL to access the item'),
10
+ size: z.number().optional().describe('File size in bytes'),
11
+ isFolder: z.boolean().describe('Whether this is a folder'),
12
+ lastModifiedDateTime: z.string().optional().describe('Last modified date'),
13
+ lastModifiedBy: z.string().optional().describe('User who last modified the item'),
14
+ parentPath: z.string().optional().describe('Path of the parent folder')
15
+ });
16
+
17
+ export let searchDrive = SlateTool.create(spec, {
18
+ name: 'Search Drive',
19
+ key: 'search_drive',
20
+ description: `Search for files and folders within a specific SharePoint document library (drive). Uses the OneDrive search API scoped to a single drive. Returns matching items with their metadata.`,
21
+ tags: {
22
+ readOnly: true,
23
+ destructive: false
24
+ }
25
+ })
26
+ .input(
27
+ z.object({
28
+ driveId: z.string().describe('Drive (document library) ID to search within'),
29
+ query: z.string().describe('Search query text')
30
+ })
31
+ )
32
+ .output(
33
+ z.object({
34
+ results: z.array(searchResultSchema).describe('Matching files and folders'),
35
+ totalCount: z.number().describe('Number of results returned')
36
+ })
37
+ )
38
+ .handleInvocation(async ctx => {
39
+ let client = new SharePointClient(ctx.auth.token);
40
+ let data = await client.searchDriveItems(ctx.input.driveId, ctx.input.query);
41
+
42
+ let results = (data.value || []).map((item: any) => ({
43
+ itemId: item.id,
44
+ fileName: item.name,
45
+ webUrl: item.webUrl,
46
+ size: item.size,
47
+ isFolder: !!item.folder,
48
+ lastModifiedDateTime: item.lastModifiedDateTime,
49
+ lastModifiedBy: item.lastModifiedBy?.user?.displayName,
50
+ parentPath: item.parentReference?.path
51
+ }));
52
+
53
+ return {
54
+ output: {
55
+ results,
56
+ totalCount: results.length
57
+ },
58
+ message: `Found **${results.length}** result(s) for "${ctx.input.query}" in the drive.`
59
+ };
60
+ })
61
+ .build();
@@ -0,0 +1,132 @@
1
+ import { SlateTool } from 'slates';
2
+ import { SharePointClient } from '../lib/client';
3
+ import { spec } from '../spec';
4
+ import { z } from 'zod';
5
+
6
+ let sharepointIdsSchema = z.object({
7
+ siteId: z.string().optional().describe('SharePoint site ID'),
8
+ siteUrl: z.string().optional().describe('SharePoint site URL'),
9
+ webId: z.string().optional().describe('SharePoint web ID'),
10
+ listId: z.string().optional().describe('SharePoint list ID'),
11
+ listItemId: z.string().optional().describe('SharePoint list item ID'),
12
+ listItemUniqueId: z.string().optional().describe('SharePoint list item unique ID'),
13
+ driveId: z.string().optional().describe('Drive ID'),
14
+ driveItemId: z.string().optional().describe('Drive item ID'),
15
+ tenantId: z.string().optional().describe('Tenant ID')
16
+ });
17
+
18
+ let searchResultSchema = z.object({
19
+ resourceId: z.string().optional().describe('ID of the matched resource'),
20
+ hitId: z.string().optional().describe('Hit ID returned by Microsoft Search'),
21
+ resourceName: z.string().optional().describe('Name or title of the matched resource'),
22
+ resourceType: z
23
+ .string()
24
+ .optional()
25
+ .describe('Type of the matched resource (e.g. driveItem, site, listItem)'),
26
+ webUrl: z.string().optional().describe('URL of the resource'),
27
+ sharepointIds: sharepointIdsSchema.optional().describe('Graph sharepointIds for the resource'),
28
+ summary: z.string().optional().describe('Search result summary/snippet'),
29
+ lastModifiedDateTime: z.string().optional().describe('Last modified date'),
30
+ lastModifiedBy: z.string().optional().describe('User who last modified the resource')
31
+ });
32
+
33
+ export let search = SlateTool.create(spec, {
34
+ name: 'Search',
35
+ key: 'search',
36
+ description: `Search across SharePoint content using the Microsoft Search API. Search for files, folders, lists, list items, or sites using KQL (Keyword Query Language) queries. Supports filtering by entity type and pagination.`,
37
+ instructions: [
38
+ 'Use **query** with KQL syntax for powerful searches, e.g. "budget filetype:xlsx" or "author:john".',
39
+ 'Use **entityTypes** to narrow results to specific types: "driveItem" (files/folders), "listItem", "list", "site".',
40
+ 'Use **from** and **size** for pagination.'
41
+ ],
42
+ tags: {
43
+ readOnly: true,
44
+ destructive: false
45
+ }
46
+ })
47
+ .input(
48
+ z.object({
49
+ query: z.string().describe('Search query string (supports KQL syntax)'),
50
+ entityTypes: z
51
+ .array(z.enum(['driveItem', 'listItem', 'list', 'site']))
52
+ .optional()
53
+ .describe('Types of entities to search for. Defaults to all types.'),
54
+ from: z.number().optional().describe('Offset for pagination (default 0)'),
55
+ size: z.number().optional().describe('Number of results to return (default 25, max 500)')
56
+ })
57
+ )
58
+ .output(
59
+ z.object({
60
+ results: z.array(searchResultSchema).describe('Search results'),
61
+ totalCount: z.number().describe('Total number of matching results (may be approximate)'),
62
+ moreResultsAvailable: z.boolean().describe('Whether there are more results to fetch')
63
+ })
64
+ )
65
+ .handleInvocation(async ctx => {
66
+ let client = new SharePointClient(ctx.auth.token);
67
+ let entityTypes = ctx.input.entityTypes?.length
68
+ ? ctx.input.entityTypes
69
+ : ['driveItem', 'listItem', 'list', 'site'];
70
+
71
+ let data = await client.search(
72
+ ctx.input.query,
73
+ entityTypes,
74
+ ctx.input.from,
75
+ ctx.input.size
76
+ );
77
+
78
+ let results: Array<any> = [];
79
+ let totalCount = 0;
80
+ let moreResultsAvailable = false;
81
+
82
+ let hitsContainers = data.value?.[0]?.hitsContainers || [];
83
+ for (let container of hitsContainers) {
84
+ totalCount += container.total || 0;
85
+ moreResultsAvailable = moreResultsAvailable || container.moreResultsAvailable || false;
86
+
87
+ for (let hit of container.hits || []) {
88
+ let resource = hit.resource || {};
89
+ let sharepointIds =
90
+ resource.sharepointIds && typeof resource.sharepointIds === 'object'
91
+ ? {
92
+ siteId: resource.sharepointIds.siteId,
93
+ siteUrl: resource.sharepointIds.siteUrl,
94
+ webId: resource.sharepointIds.webId,
95
+ listId: resource.sharepointIds.listId,
96
+ listItemId: resource.sharepointIds.listItemId,
97
+ listItemUniqueId: resource.sharepointIds.listItemUniqueId,
98
+ driveId: resource.sharepointIds.driveId,
99
+ driveItemId: resource.sharepointIds.driveItemId,
100
+ tenantId: resource.sharepointIds.tenantId
101
+ }
102
+ : undefined;
103
+ results.push({
104
+ resourceId:
105
+ resource.id ||
106
+ sharepointIds?.driveItemId ||
107
+ sharepointIds?.listItemId ||
108
+ sharepointIds?.listId ||
109
+ sharepointIds?.siteId ||
110
+ hit.hitId,
111
+ hitId: hit.hitId,
112
+ resourceName: resource.name || resource.displayName,
113
+ resourceType: hit.resource?.['@odata.type']?.replace('#microsoft.graph.', ''),
114
+ webUrl: resource.webUrl,
115
+ sharepointIds,
116
+ summary: hit.summary,
117
+ lastModifiedDateTime: resource.lastModifiedDateTime,
118
+ lastModifiedBy: resource.lastModifiedBy?.user?.displayName
119
+ });
120
+ }
121
+ }
122
+
123
+ return {
124
+ output: {
125
+ results,
126
+ totalCount,
127
+ moreResultsAvailable
128
+ },
129
+ message: `Found **${totalCount}** result(s) for "${ctx.input.query}". Returned ${results.length} in this page.`
130
+ };
131
+ })
132
+ .build();
@@ -0,0 +1,202 @@
1
+ import { SlateTrigger, SlateDefaultPollingIntervalSeconds } from 'slates';
2
+ import { SharePointClient } from '../lib/client';
3
+ import { spec } from '../spec';
4
+ import { z } from 'zod';
5
+
6
+ export let driveItemChanges = SlateTrigger.create(spec, {
7
+ name: 'Drive Item Changes',
8
+ key: 'drive_item_changes',
9
+ description:
10
+ 'Triggers when files or folders are created, updated, or deleted in a SharePoint document library. Polls the drive delta API to detect changes.'
11
+ })
12
+ .input(
13
+ z.object({
14
+ driveId: z.string().describe('Drive ID being monitored'),
15
+ changeType: z
16
+ .enum(['created', 'updated', 'deleted'])
17
+ .describe('Type of change detected'),
18
+ itemId: z.string().describe('Drive item ID'),
19
+ fileName: z.string().optional().describe('Name of the file or folder'),
20
+ isFolder: z.boolean().optional().describe('Whether this item is a folder'),
21
+ webUrl: z.string().optional().describe('URL of the item'),
22
+ size: z.number().optional().describe('File size in bytes'),
23
+ mimeType: z.string().optional().describe('MIME type of the file'),
24
+ lastModifiedDateTime: z.string().optional().describe('Last modified date'),
25
+ lastModifiedBy: z.string().optional().describe('User who modified the item'),
26
+ parentPath: z.string().optional().describe('Parent folder path')
27
+ })
28
+ )
29
+ .output(
30
+ z.object({
31
+ driveId: z.string().describe('Drive ID'),
32
+ itemId: z.string().describe('Drive item ID'),
33
+ changeType: z.enum(['created', 'updated', 'deleted']).describe('Type of change'),
34
+ fileName: z.string().optional().describe('Name of the file or folder'),
35
+ isFolder: z.boolean().optional().describe('Whether this item is a folder'),
36
+ webUrl: z.string().optional().describe('URL of the item'),
37
+ size: z.number().optional().describe('File size in bytes'),
38
+ mimeType: z.string().optional().describe('MIME type of the file'),
39
+ lastModifiedDateTime: z.string().optional().describe('Last modified date'),
40
+ lastModifiedBy: z.string().optional().describe('User who modified the item'),
41
+ parentPath: z.string().optional().describe('Parent folder path')
42
+ })
43
+ )
44
+ .polling({
45
+ options: {
46
+ intervalInSeconds: SlateDefaultPollingIntervalSeconds
47
+ },
48
+
49
+ pollEvents: async ctx => {
50
+ let client = new SharePointClient(ctx.auth.token);
51
+ let state = ctx.state as {
52
+ deltaToken?: string;
53
+ knownItems?: Record<string, string>;
54
+ driveId?: string;
55
+ initialized?: boolean;
56
+ } | null;
57
+
58
+ let driveId = state?.driveId;
59
+
60
+ if (!driveId) {
61
+ return {
62
+ inputs: [],
63
+ updatedState: {
64
+ ...state,
65
+ initialized: false
66
+ }
67
+ };
68
+ }
69
+
70
+ let deltaToken = state?.deltaToken;
71
+ let knownItems = state?.knownItems || {};
72
+
73
+ let data = await client.getDelta(driveId, deltaToken);
74
+ let items = data.value || [];
75
+ let newDeltaToken = data['@odata.deltaLink'];
76
+ let nextLink = data['@odata.nextLink'];
77
+
78
+ // Process all pages
79
+ let allItems = [...items];
80
+ let currentNextLink = nextLink;
81
+ while (currentNextLink) {
82
+ let nextData = await client.getDelta(driveId, currentNextLink);
83
+ allItems = allItems.concat(nextData.value || []);
84
+ currentNextLink = nextData['@odata.nextLink'];
85
+ if (nextData['@odata.deltaLink']) {
86
+ newDeltaToken = nextData['@odata.deltaLink'];
87
+ }
88
+ }
89
+
90
+ if (!state?.initialized) {
91
+ let updatedKnown: Record<string, string> = {};
92
+ for (let item of allItems) {
93
+ let isDeleted = item.deleted !== undefined || item['@removed'] !== undefined;
94
+ if (!isDeleted) {
95
+ updatedKnown[item.id] = item.lastModifiedDateTime || '';
96
+ }
97
+ }
98
+ return {
99
+ inputs: [],
100
+ updatedState: {
101
+ driveId,
102
+ deltaToken: newDeltaToken,
103
+ knownItems: updatedKnown,
104
+ initialized: true
105
+ }
106
+ };
107
+ }
108
+
109
+ let inputs: Array<{
110
+ driveId: string;
111
+ changeType: 'created' | 'updated' | 'deleted';
112
+ itemId: string;
113
+ fileName?: string;
114
+ isFolder?: boolean;
115
+ webUrl?: string;
116
+ size?: number;
117
+ mimeType?: string;
118
+ lastModifiedDateTime?: string;
119
+ lastModifiedBy?: string;
120
+ parentPath?: string;
121
+ }> = [];
122
+
123
+ let updatedKnown = { ...knownItems };
124
+
125
+ for (let item of allItems) {
126
+ let isDeleted = item.deleted !== undefined || item['@removed'] !== undefined;
127
+
128
+ if (isDeleted) {
129
+ if (knownItems[item.id]) {
130
+ inputs.push({
131
+ driveId,
132
+ changeType: 'deleted',
133
+ itemId: item.id
134
+ });
135
+ delete updatedKnown[item.id];
136
+ }
137
+ } else if (!knownItems[item.id]) {
138
+ inputs.push({
139
+ driveId,
140
+ changeType: 'created',
141
+ itemId: item.id,
142
+ fileName: item.name,
143
+ isFolder: !!item.folder,
144
+ webUrl: item.webUrl,
145
+ size: item.size,
146
+ mimeType: item.file?.mimeType,
147
+ lastModifiedDateTime: item.lastModifiedDateTime,
148
+ lastModifiedBy: item.lastModifiedBy?.user?.displayName,
149
+ parentPath: item.parentReference?.path
150
+ });
151
+ updatedKnown[item.id] = item.lastModifiedDateTime || '';
152
+ } else {
153
+ inputs.push({
154
+ driveId,
155
+ changeType: 'updated',
156
+ itemId: item.id,
157
+ fileName: item.name,
158
+ isFolder: !!item.folder,
159
+ webUrl: item.webUrl,
160
+ size: item.size,
161
+ mimeType: item.file?.mimeType,
162
+ lastModifiedDateTime: item.lastModifiedDateTime,
163
+ lastModifiedBy: item.lastModifiedBy?.user?.displayName,
164
+ parentPath: item.parentReference?.path
165
+ });
166
+ updatedKnown[item.id] = item.lastModifiedDateTime || '';
167
+ }
168
+ }
169
+
170
+ return {
171
+ inputs,
172
+ updatedState: {
173
+ driveId,
174
+ deltaToken: newDeltaToken,
175
+ knownItems: updatedKnown,
176
+ initialized: true
177
+ }
178
+ };
179
+ },
180
+
181
+ handleEvent: async ctx => {
182
+ let resourceType = ctx.input.isFolder ? 'folder' : 'file';
183
+ return {
184
+ type: `drive_item.${ctx.input.changeType}`,
185
+ id: `${ctx.input.driveId}_${ctx.input.itemId}_${ctx.input.lastModifiedDateTime || Date.now()}`,
186
+ output: {
187
+ driveId: ctx.input.driveId,
188
+ itemId: ctx.input.itemId,
189
+ changeType: ctx.input.changeType,
190
+ fileName: ctx.input.fileName,
191
+ isFolder: ctx.input.isFolder,
192
+ webUrl: ctx.input.webUrl,
193
+ size: ctx.input.size,
194
+ mimeType: ctx.input.mimeType,
195
+ lastModifiedDateTime: ctx.input.lastModifiedDateTime,
196
+ lastModifiedBy: ctx.input.lastModifiedBy,
197
+ parentPath: ctx.input.parentPath
198
+ }
199
+ };
200
+ }
201
+ })
202
+ .build();
@@ -0,0 +1,67 @@
1
+ import { SlateTrigger } from 'slates';
2
+ import { spec } from '../spec';
3
+ import { z } from 'zod';
4
+
5
+ /**
6
+ * Generic inbound webhook for providers without a tailored webhook trigger yet.
7
+ * POST JSON is parsed into `payload` (non-objects are wrapped as { _value }).
8
+ * Refine in the workflow mapper or replace with a provider-specific trigger.
9
+ */
10
+ export let inboundWebhook = SlateTrigger.create(spec, {
11
+ name: 'Inbound Webhook',
12
+ key: 'inbound_webhook',
13
+ description:
14
+ 'Receives HTTP POST at the Slates webhook URL. Parses JSON into payload (or stores raw body if not JSON). Configure your provider to POST here when supported.'
15
+ })
16
+ .input(
17
+ z.object({
18
+ payload: z
19
+ .record(z.string(), z.any())
20
+ .describe('Parsed JSON object from the request body'),
21
+ rawBody: z.string().optional().describe('Raw body when JSON parsing failed'),
22
+ contentType: z.string().optional().describe('Content-Type header')
23
+ })
24
+ )
25
+ .output(
26
+ z.object({
27
+ payload: z.record(z.string(), z.any()),
28
+ rawBody: z.string().optional()
29
+ })
30
+ )
31
+ .webhook({
32
+ handleRequest: async ctx => {
33
+ let contentType = ctx.request.headers.get('content-type') ?? '';
34
+ let text = await ctx.request.text();
35
+ if (!text || !text.trim()) {
36
+ return {
37
+ inputs: [{ payload: {}, contentType }]
38
+ };
39
+ }
40
+ try {
41
+ let parsed = JSON.parse(text);
42
+ let payload =
43
+ parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
44
+ ? parsed
45
+ : { _value: parsed };
46
+ return {
47
+ inputs: [{ payload, contentType }]
48
+ };
49
+ } catch {
50
+ return {
51
+ inputs: [{ payload: {}, rawBody: text, contentType }]
52
+ };
53
+ }
54
+ },
55
+
56
+ handleEvent: async ctx => {
57
+ return {
58
+ type: 'webhook.inbound',
59
+ id: `inbound-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
60
+ output: {
61
+ payload: ctx.input.payload,
62
+ rawBody: ctx.input.rawBody
63
+ }
64
+ };
65
+ }
66
+ })
67
+ .build();
@@ -0,0 +1,3 @@
1
+ export * from './list-item-changes';
2
+ export * from './drive-item-changes';
3
+ export * from './inbound-webhook';