@tangle-network/agent-integrations 0.69.0 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/data/bundled-adapter-manifests.json +1 -1
  2. package/dist/{adapters-JMJ8fU1b.js → adapters-CURjrtYm.js} +1033 -19
  3. package/dist/{adapters-JMJ8fU1b.js.map → adapters-CURjrtYm.js.map} +1 -1
  4. package/dist/{bundled-manifest-data-Bv0ejHMQ.js → bundled-manifest-data-DUxxlN37.js} +447 -2
  5. package/dist/{bundled-manifest-data-Bv0ejHMQ.js.map → bundled-manifest-data-DUxxlN37.js.map} +1 -1
  6. package/dist/connectors/adapters/index.d.ts +2 -2
  7. package/dist/connectors/adapters/index.js +2 -2
  8. package/dist/connectors/index.d.ts +3 -3
  9. package/dist/connectors/index.js +3 -3
  10. package/dist/{consumer-BvaFhBN_.d.ts → consumer-DaKqPDG-.d.ts} +2 -2
  11. package/dist/{consumer-BvaFhBN_.d.ts.map → consumer-DaKqPDG-.d.ts.map} +1 -1
  12. package/dist/consumer.d.ts +1 -1
  13. package/dist/{index-D0uvn67q.d.ts → index-b05PzTtX.d.ts} +2 -2
  14. package/dist/{index-D0uvn67q.d.ts.map → index-b05PzTtX.d.ts.map} +1 -1
  15. package/dist/{index-DkNU5tcV.d.ts → index-udV3gGjx.d.ts} +14 -2
  16. package/dist/index-udV3gGjx.d.ts.map +1 -0
  17. package/dist/index.d.ts +4 -4
  18. package/dist/index.js +5 -5
  19. package/dist/internal/bundled-manifest-runtime.js +1 -1
  20. package/dist/mcp.d.ts +1 -1
  21. package/dist/mcp.js +1 -1
  22. package/dist/registry.js +1 -1
  23. package/dist/runtime.d.ts +1 -1
  24. package/dist/sandbox.d.ts +1 -1
  25. package/dist/{specs-BqZdi7Jk.js → specs-61KwR2bK.js} +2 -2
  26. package/dist/{specs-BqZdi7Jk.js.map → specs-61KwR2bK.js.map} +1 -1
  27. package/dist/specs.js +1 -1
  28. package/dist/{src-BFIGHp-s.js → src-iUK2d9AZ.js} +3 -3
  29. package/dist/{src-BFIGHp-s.js.map → src-iUK2d9AZ.js.map} +1 -1
  30. package/dist/triggers/index.d.ts +78 -0
  31. package/dist/triggers/index.d.ts.map +1 -0
  32. package/dist/triggers/index.js +730 -0
  33. package/dist/triggers/index.js.map +1 -0
  34. package/dist/webhooks/index.d.ts +1 -1
  35. package/dist/webhooks/index.d.ts.map +1 -1
  36. package/dist/webhooks/index.js +76 -1
  37. package/dist/webhooks/index.js.map +1 -1
  38. package/docs/integration-execution-audit.md +17 -8
  39. package/docs/integration-execution-matrix.json +537 -28
  40. package/package.json +6 -1
  41. package/dist/index-DkNU5tcV.d.ts.map +0 -1
@@ -0,0 +1,730 @@
1
+ //#region src/triggers/definitions.ts
2
+ /**
3
+ * The hosted triggers, one per (connector, event), each over that connector's
4
+ * existing READ capabilities. A definition lists candidate records created at
5
+ * or after `since`; `pollHostedTrigger` filters, orders and caps them.
6
+ */
7
+ const EMAIL = /^[^\s@<>()[\]\\,;:"]{1,64}@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i;
8
+ const POLL_SECONDS = 120;
9
+ function object(value) {
10
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
11
+ }
12
+ function list(value) {
13
+ return Array.isArray(value) ? value.map(object) : [];
14
+ }
15
+ function text(value, max = 500) {
16
+ return typeof value === "string" && value.trim() ? value.trim().slice(0, max) : null;
17
+ }
18
+ /** `Ada Lovelace <ada@example.com>` and bare addresses alike. */
19
+ function email(value) {
20
+ const raw = text(value, 400);
21
+ if (!raw) return null;
22
+ const candidate = (/<([^<>]+)>/.exec(raw)?.[1] ?? raw).trim().toLowerCase();
23
+ return EMAIL.test(candidate) ? candidate : null;
24
+ }
25
+ function displayName(value) {
26
+ const raw = text(value, 400);
27
+ return raw && raw.includes("<") ? text(raw.slice(0, raw.indexOf("<")).replace(/^"|"$/g, "")) : null;
28
+ }
29
+ /** An ISO time from epoch seconds or milliseconds, a numeric string, or a date string. */
30
+ function isoTime(value) {
31
+ if (typeof value === "number" && Number.isFinite(value)) return new Date(value > 0xe8d4a51000 ? value : value * 1e3).toISOString();
32
+ const raw = text(value, 100);
33
+ if (!raw) return null;
34
+ if (/^\d{10,13}$/.test(raw)) {
35
+ const n = Number(raw);
36
+ return new Date(n > 0xe8d4a51000 ? n : n * 1e3).toISOString();
37
+ }
38
+ const parsed = Date.parse(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(raw) ? `${raw.replace(" ", "T")}Z` : raw);
39
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
40
+ }
41
+ function person(input) {
42
+ const address = email(input.email);
43
+ const name = text(input.name, 200);
44
+ const company = text(input.company, 200);
45
+ return address || name || company ? {
46
+ email: address,
47
+ name,
48
+ company
49
+ } : null;
50
+ }
51
+ function seconds(isoValue) {
52
+ return Math.floor(Date.parse(isoValue) / 1e3);
53
+ }
54
+ /** Senders that are never a person writing in. */
55
+ const AUTOMATED_SENDER = /^(no-?reply|do-?not-?reply|mailer-daemon|postmaster|notifications?|bounces?)([+.\-_][^@]*)?@/i;
56
+ function mailRecord(input) {
57
+ const occurredAt = isoTime(input.at);
58
+ const from = email(input.from);
59
+ if (!occurredAt || !from || AUTOMATED_SENDER.test(from)) return null;
60
+ const subject = text(input.subject, 300);
61
+ const to = (Array.isArray(input.to) ? input.to : typeof input.to === "string" ? input.to.split(",") : []).map(email).filter((value) => Boolean(value));
62
+ return {
63
+ key: input.key,
64
+ occurredAt,
65
+ person: {
66
+ email: from,
67
+ name: text(input.fromName, 200) ?? displayName(input.from),
68
+ company: null
69
+ },
70
+ summary: subject ? `Email: ${subject}` : "Email with no subject",
71
+ record: {
72
+ messageId: input.key,
73
+ threadId: text(input.threadId, 200),
74
+ from,
75
+ to,
76
+ subject,
77
+ preview: text(input.preview, 1e3)
78
+ }
79
+ };
80
+ }
81
+ const gmail = {
82
+ provider: "gmail",
83
+ event: "gmail.message.received",
84
+ description: "A new message arrived in the inbox from someone other than the account itself.",
85
+ intervalSeconds: POLL_SECONDS,
86
+ reads: ["list_messages"],
87
+ async list({ read, since }) {
88
+ return list(object(await read("list_messages", {
89
+ query: `in:inbox -from:me after:${seconds(since)}`,
90
+ maxResults: 50
91
+ })).messages).flatMap((message) => {
92
+ const id = text(message.id, 200);
93
+ const record = id ? mailRecord({
94
+ key: id,
95
+ at: message.internalDate,
96
+ from: message.from,
97
+ to: message.to,
98
+ subject: message.subject,
99
+ preview: message.snippet,
100
+ threadId: message.threadId
101
+ }) : null;
102
+ return record ? [record] : [];
103
+ });
104
+ }
105
+ };
106
+ const outlook = {
107
+ provider: "outlook-mail",
108
+ event: "outlook-mail.message.received",
109
+ description: "A new message arrived in the inbox.",
110
+ intervalSeconds: POLL_SECONDS,
111
+ reads: ["list_messages"],
112
+ async list({ read }) {
113
+ return list(object(await read("list_messages", {
114
+ folder: "inbox",
115
+ top: 50
116
+ })).messages).flatMap((message) => {
117
+ const id = text(message.id, 400);
118
+ const record = id ? mailRecord({
119
+ key: id,
120
+ at: message.receivedDateTime,
121
+ from: message.from,
122
+ fromName: message.fromName,
123
+ to: message.to,
124
+ subject: message.subject,
125
+ preview: message.bodyPreview,
126
+ threadId: message.conversationId
127
+ }) : null;
128
+ return record ? [record] : [];
129
+ });
130
+ }
131
+ };
132
+ async function hubspotPages(read, capability, key, since) {
133
+ const rows = [];
134
+ let after = null;
135
+ for (let page = 0; page < 3; page++) {
136
+ const data = object(await read(capability, {
137
+ createdAfter: since,
138
+ limit: 100,
139
+ ...after ? { after } : {}
140
+ }));
141
+ rows.push(...list(data[key]));
142
+ after = text(data.after, 200);
143
+ if (!after) break;
144
+ }
145
+ return rows;
146
+ }
147
+ const hubspotContact = {
148
+ provider: "hubspot",
149
+ event: "hubspot.contact.created",
150
+ description: "A contact was created, from a form, an import or an integration.",
151
+ intervalSeconds: POLL_SECONDS,
152
+ reads: ["list_new_contacts"],
153
+ async list({ read, since }) {
154
+ return (await hubspotPages(read, "list_new_contacts", "contacts", since)).flatMap((contact) => {
155
+ const id = text(contact.id, 100);
156
+ const occurredAt = isoTime(contact.createdAt);
157
+ if (!id || !occurredAt) return [];
158
+ return [{
159
+ key: `contact:${id}`,
160
+ occurredAt,
161
+ person: person({
162
+ email: contact.email,
163
+ name: contact.name,
164
+ company: contact.company
165
+ }),
166
+ summary: `New HubSpot contact${contact.source ? ` from ${String(contact.source).toLowerCase().replaceAll("_", " ")}` : ""}`,
167
+ record: {
168
+ contactId: id,
169
+ jobTitle: text(contact.jobTitle, 200),
170
+ lifecycleStage: text(contact.lifecycleStage, 100),
171
+ source: text(contact.source, 100)
172
+ }
173
+ }];
174
+ });
175
+ }
176
+ };
177
+ const hubspotDeal = {
178
+ provider: "hubspot",
179
+ event: "hubspot.deal.created",
180
+ description: "A deal was created.",
181
+ intervalSeconds: POLL_SECONDS,
182
+ reads: ["list_new_deals"],
183
+ async list({ read, since }) {
184
+ return (await hubspotPages(read, "list_new_deals", "deals", since)).flatMap((deal) => {
185
+ const id = text(deal.id, 100);
186
+ const occurredAt = isoTime(deal.createdAt);
187
+ if (!id || !occurredAt) return [];
188
+ return [{
189
+ key: `deal:${id}`,
190
+ occurredAt,
191
+ person: null,
192
+ summary: `New HubSpot deal: ${text(deal.name, 200) ?? id}`,
193
+ record: {
194
+ dealId: id,
195
+ name: text(deal.name, 200),
196
+ stage: text(deal.stage, 100),
197
+ pipeline: text(deal.pipeline, 100),
198
+ amount: deal.amount ?? null,
199
+ ownerId: deal.ownerId ?? null
200
+ }
201
+ }];
202
+ });
203
+ }
204
+ };
205
+ function attioValue(values, slug, field) {
206
+ return object(list(values[slug])[0])[field];
207
+ }
208
+ const attioPerson = {
209
+ provider: "attio",
210
+ event: "attio.person.created",
211
+ description: "A person record was created.",
212
+ intervalSeconds: POLL_SECONDS,
213
+ reads: ["records.query"],
214
+ async list({ read, since }) {
215
+ return list(object(await read("records.query", {
216
+ object: "people",
217
+ filter: { created_at: { $gte: since } },
218
+ sorts: [{
219
+ attribute: "created_at",
220
+ direction: "asc"
221
+ }],
222
+ limit: 100
223
+ })).data).flatMap((row) => {
224
+ const id = text(object(row.id).record_id, 100);
225
+ const occurredAt = isoTime(row.created_at);
226
+ if (!id || !occurredAt) return [];
227
+ const values = object(row.values);
228
+ const name = attioValue(values, "name", "full_name");
229
+ return [{
230
+ key: `person:${id}`,
231
+ occurredAt,
232
+ person: person({
233
+ email: attioValue(values, "email_addresses", "email_address"),
234
+ name,
235
+ company: null
236
+ }),
237
+ summary: `New Attio person${typeof name === "string" ? `: ${name}` : ""}`,
238
+ record: {
239
+ recordId: id,
240
+ jobTitle: text(attioValue(values, "job_title", "value"), 200)
241
+ }
242
+ }];
243
+ });
244
+ }
245
+ };
246
+ async function pipedriveRecent(read, capability, since) {
247
+ const rows = [];
248
+ const sinceMs = Date.parse(since);
249
+ for (let start = 0; start < 300; start += 100) {
250
+ const page = list(object(await read(capability, {
251
+ sort: "add_time DESC",
252
+ start,
253
+ limit: 100
254
+ })).data);
255
+ rows.push(...page);
256
+ const oldest = page.at(-1);
257
+ if (page.length < 100 || !oldest || Date.parse(isoTime(oldest.add_time) ?? "") < sinceMs) break;
258
+ }
259
+ return rows;
260
+ }
261
+ function pipedriveEmail(value) {
262
+ const entries = list(value);
263
+ return (entries.find((entry) => entry.primary === true) ?? entries[0])?.value;
264
+ }
265
+ const pipedrivePerson = {
266
+ provider: "pipedrive",
267
+ event: "pipedrive.person.created",
268
+ description: "A person was added.",
269
+ intervalSeconds: POLL_SECONDS,
270
+ reads: ["persons.list"],
271
+ async list({ read, since }) {
272
+ return (await pipedriveRecent(read, "persons.list", since)).flatMap((row) => {
273
+ const id = row.id !== void 0 ? String(row.id) : null;
274
+ const occurredAt = isoTime(row.add_time);
275
+ if (!id || !occurredAt) return [];
276
+ const company = text(row.org_name, 200) ?? text(object(row.org_id).name, 200);
277
+ return [{
278
+ key: `person:${id}`,
279
+ occurredAt,
280
+ person: person({
281
+ email: pipedriveEmail(row.email),
282
+ name: row.name,
283
+ company
284
+ }),
285
+ summary: `New Pipedrive person${row.name ? `: ${String(row.name)}` : ""}`,
286
+ record: {
287
+ personId: id,
288
+ ownerId: object(row.owner_id).id ?? row.owner_id ?? null
289
+ }
290
+ }];
291
+ });
292
+ }
293
+ };
294
+ const pipedriveDeal = {
295
+ provider: "pipedrive",
296
+ event: "pipedrive.deal.created",
297
+ description: "A deal was added.",
298
+ intervalSeconds: POLL_SECONDS,
299
+ reads: ["deals.list"],
300
+ async list({ read, since }) {
301
+ return (await pipedriveRecent(read, "deals.list", since)).flatMap((row) => {
302
+ const id = row.id !== void 0 ? String(row.id) : null;
303
+ const occurredAt = isoTime(row.add_time);
304
+ if (!id || !occurredAt) return [];
305
+ const contact = object(row.person_id);
306
+ return [{
307
+ key: `deal:${id}`,
308
+ occurredAt,
309
+ person: person({
310
+ email: pipedriveEmail(contact.email),
311
+ name: contact.name ?? row.person_name,
312
+ company: text(object(row.org_id).name, 200) ?? row.org_name
313
+ }),
314
+ summary: `New Pipedrive deal: ${text(row.title, 200) ?? id}`,
315
+ record: {
316
+ dealId: id,
317
+ title: text(row.title, 200),
318
+ value: row.value ?? null,
319
+ currency: text(row.currency, 10),
320
+ stageId: row.stage_id ?? null
321
+ }
322
+ }];
323
+ });
324
+ }
325
+ };
326
+ /** A form answer's value as text, whatever its type. */
327
+ function answerText(answer) {
328
+ const choice = object(answer.choice);
329
+ const choices = object(answer.choices);
330
+ const value = answer.email ?? answer.text ?? answer.url ?? answer.phone_number ?? answer.number ?? answer.date ?? answer.boolean ?? choice.label ?? (Array.isArray(choices.labels) ? choices.labels.join(", ") : void 0);
331
+ return value === void 0 || value === null ? null : String(value).slice(0, 1e3);
332
+ }
333
+ /** Name, company and title from a form's answers, matched on the question titles. */
334
+ function formPerson(fields) {
335
+ const pick = (pattern) => fields.find((field) => pattern.test(field.title))?.value ?? null;
336
+ return person({
337
+ email: fields.find((field) => field.isEmail)?.value ?? fields.map((field) => email(field.value)).find(Boolean),
338
+ name: pick(/\bname\b/i),
339
+ company: pick(/company|organi[sz]ation|business|team/i)
340
+ });
341
+ }
342
+ const HOSTED_TRIGGER_DEFINITIONS = [
343
+ gmail,
344
+ outlook,
345
+ hubspotContact,
346
+ hubspotDeal,
347
+ attioPerson,
348
+ pipedrivePerson,
349
+ pipedriveDeal,
350
+ {
351
+ provider: "typeform",
352
+ event: "typeform.response.submitted",
353
+ description: "A completed response was submitted to any of the account's forms.",
354
+ intervalSeconds: POLL_SECONDS,
355
+ reads: [
356
+ "forms.list",
357
+ "forms.get",
358
+ "responses.list"
359
+ ],
360
+ async list({ read, since }) {
361
+ const forms = list(object(await read("forms.list", { page_size: 20 })).items).slice(0, 10);
362
+ const records = [];
363
+ for (const form of forms) {
364
+ const formId = text(form.id, 100);
365
+ if (!formId) continue;
366
+ const responses = list(object(await read("responses.list", {
367
+ form_id: formId,
368
+ since,
369
+ completed: true,
370
+ sort: "submitted_at,asc",
371
+ page_size: 50
372
+ })).items);
373
+ if (responses.length === 0) continue;
374
+ const definition = object(await read("forms.get", { form_id: formId }));
375
+ const titles = new Map(list(definition.fields).map((field) => [String(field.id), text(field.title, 200) ?? String(field.ref ?? "")]));
376
+ for (const response of responses) {
377
+ const token = text(response.token, 200) ?? text(response.response_id, 200);
378
+ const occurredAt = isoTime(response.submitted_at);
379
+ if (!token || !occurredAt) continue;
380
+ const fields = list(response.answers).flatMap((answer) => {
381
+ const value = answerText(answer);
382
+ const field = object(answer.field);
383
+ return value ? [{
384
+ title: titles.get(String(field.id)) ?? String(field.ref ?? "answer"),
385
+ value,
386
+ isEmail: answer.type === "email"
387
+ }] : [];
388
+ });
389
+ records.push({
390
+ key: `response:${token}`,
391
+ occurredAt,
392
+ person: formPerson(fields),
393
+ summary: `Typeform response to "${text(form.title, 200) ?? formId}"`,
394
+ record: {
395
+ formId,
396
+ formTitle: text(form.title, 200),
397
+ responseId: token,
398
+ answers: fields.map(({ title, value }) => ({
399
+ question: title,
400
+ answer: value
401
+ }))
402
+ }
403
+ });
404
+ }
405
+ }
406
+ return records;
407
+ }
408
+ },
409
+ {
410
+ provider: "tally",
411
+ event: "tally.submission.created",
412
+ description: "A completed submission arrived on any of the account's forms.",
413
+ intervalSeconds: POLL_SECONDS,
414
+ reads: ["forms.list", "submissions.list"],
415
+ async list({ read, since }) {
416
+ const forms = list(object(await read("forms.list", { limit: 20 })).items).slice(0, 10);
417
+ const records = [];
418
+ for (const form of forms) {
419
+ const formId = text(form.id, 100);
420
+ if (!formId) continue;
421
+ const data = object(await read("submissions.list", {
422
+ formId,
423
+ filter: "completed",
424
+ startDate: since,
425
+ limit: 50
426
+ }));
427
+ const questions = new Map(list(data.questions).map((question) => [String(question.id), {
428
+ title: text(question.title, 200) ?? "answer",
429
+ type: String(question.type ?? "")
430
+ }]));
431
+ for (const submission of list(data.submissions)) {
432
+ const id = text(submission.id, 100);
433
+ const occurredAt = isoTime(submission.submittedAt ?? submission.createdAt);
434
+ if (!id || !occurredAt) continue;
435
+ const fields = list(submission.responses).flatMap((response) => {
436
+ const question = questions.get(String(response.questionId));
437
+ const raw = response.value ?? response.answer;
438
+ const value = raw === void 0 || raw === null ? null : (Array.isArray(raw) ? raw.join(", ") : typeof raw === "object" ? JSON.stringify(raw) : String(raw)).slice(0, 1e3);
439
+ return value ? [{
440
+ title: question?.title ?? "answer",
441
+ value,
442
+ isEmail: question?.type === "INPUT_EMAIL"
443
+ }] : [];
444
+ });
445
+ records.push({
446
+ key: `submission:${id}`,
447
+ occurredAt,
448
+ person: formPerson(fields),
449
+ summary: `Tally submission to "${text(form.name, 200) ?? formId}"`,
450
+ record: {
451
+ formId,
452
+ formName: text(form.name, 200),
453
+ submissionId: id,
454
+ answers: fields.map(({ title, value }) => ({
455
+ question: title,
456
+ answer: value
457
+ }))
458
+ }
459
+ });
460
+ }
461
+ }
462
+ return records;
463
+ }
464
+ },
465
+ {
466
+ provider: "webflow",
467
+ event: "webflow.form.submitted",
468
+ description: "A form on any of the account's sites was submitted.",
469
+ intervalSeconds: POLL_SECONDS,
470
+ reads: [
471
+ "sites.list",
472
+ "forms.list",
473
+ "forms.submissions"
474
+ ],
475
+ async list({ read }) {
476
+ const sites = list(object(await read("sites.list", {})).sites).slice(0, 5);
477
+ const records = [];
478
+ for (const site of sites) {
479
+ const siteId = text(site.id, 100);
480
+ if (!siteId) continue;
481
+ const forms = list(object(await read("forms.list", {
482
+ siteId,
483
+ limit: 20
484
+ })).forms).slice(0, 10);
485
+ for (const form of forms) {
486
+ const formId = text(form.id, 100);
487
+ if (!formId) continue;
488
+ const submissions = [];
489
+ for (let offset = 0; offset < 1e3; offset += 100) {
490
+ const page = list(object(await read("forms.submissions", {
491
+ formId,
492
+ offset,
493
+ limit: 100
494
+ })).formSubmissions);
495
+ submissions.push(...page);
496
+ if (page.length < 100) break;
497
+ }
498
+ for (const submission of submissions) {
499
+ const id = text(submission.id, 100);
500
+ const occurredAt = isoTime(submission.dateSubmitted);
501
+ if (!id || !occurredAt) continue;
502
+ const fields = Object.entries(object(submission.formResponse)).flatMap(([title, value]) => value === null || value === void 0 || value === "" ? [] : [{
503
+ title,
504
+ value: String(value).slice(0, 1e3),
505
+ isEmail: /e-?mail/i.test(title)
506
+ }]);
507
+ records.push({
508
+ key: `submission:${id}`,
509
+ occurredAt,
510
+ person: formPerson(fields),
511
+ summary: `Webflow form "${text(form.displayName, 200) ?? formId}" submitted on ${text(site.displayName, 200) ?? siteId}`,
512
+ record: {
513
+ siteId,
514
+ formId,
515
+ submissionId: id,
516
+ answers: fields.map(({ title, value }) => ({
517
+ question: title,
518
+ answer: value
519
+ }))
520
+ }
521
+ });
522
+ }
523
+ }
524
+ }
525
+ return records;
526
+ }
527
+ },
528
+ {
529
+ provider: "calendly",
530
+ event: "calendly.invitee.created",
531
+ description: "Someone booked a meeting.",
532
+ intervalSeconds: POLL_SECONDS,
533
+ reads: [
534
+ "user.get-current",
535
+ "scheduled-events.list",
536
+ "scheduled-events.list-invitees"
537
+ ],
538
+ async list({ read, since }) {
539
+ const me = object(await read("user.get-current", {}));
540
+ const user = text(object(me.resource).uri, 300) ?? text(me.uri, 300);
541
+ if (!user) throw new Error("calendly.invitee.created: Calendly returned no current user");
542
+ const events = list(object(await read("scheduled-events.list", {
543
+ user,
544
+ status: "active",
545
+ min_start_time: since,
546
+ count: 100
547
+ })).collection);
548
+ const records = [];
549
+ const sinceMs = Date.parse(since);
550
+ for (const event of events) {
551
+ const uri = text(event.uri, 300);
552
+ const created = isoTime(event.created_at);
553
+ if (!uri || !created || Date.parse(created) < sinceMs) continue;
554
+ const uuid = uri.split("/").pop();
555
+ for (const invitee of list(object(await read("scheduled-events.list-invitees", {
556
+ uuid,
557
+ count: 25
558
+ })).collection)) {
559
+ const occurredAt = isoTime(invitee.created_at) ?? created;
560
+ const answers = list(invitee.questions_and_answers).map((qa) => ({
561
+ question: text(qa.question, 200) ?? "",
562
+ answer: text(qa.answer, 1e3) ?? ""
563
+ }));
564
+ const inviteeKey = text(invitee.uri, 300) ?? `${uuid}:${email(invitee.email) ?? ""}`;
565
+ records.push({
566
+ key: `invitee:${inviteeKey}`,
567
+ occurredAt,
568
+ person: person({
569
+ email: invitee.email,
570
+ name: invitee.name,
571
+ company: answers.find((qa) => /company|organi[sz]ation/i.test(qa.question))?.answer
572
+ }),
573
+ summary: `Booked "${text(event.name, 200) ?? "a meeting"}" for ${text(event.start_time, 40) ?? "a future time"}`,
574
+ record: {
575
+ eventUri: uri,
576
+ start: text(event.start_time, 40),
577
+ end: text(event.end_time, 40),
578
+ answers
579
+ }
580
+ });
581
+ }
582
+ }
583
+ return records;
584
+ }
585
+ },
586
+ {
587
+ provider: "cal-com",
588
+ event: "cal-com.booking.created",
589
+ description: "Someone booked a meeting.",
590
+ intervalSeconds: POLL_SECONDS,
591
+ reads: ["bookings.list"],
592
+ async list({ read }) {
593
+ const bookings = [];
594
+ let cursor = null;
595
+ for (let page = 0; page < 3; page++) {
596
+ const data = object(await read("bookings.list", {
597
+ status: "upcoming",
598
+ limit: 100,
599
+ ...cursor ? { cursor } : {}
600
+ }));
601
+ bookings.push(...list(Array.isArray(data.data) ? data.data : object(data.data).bookings ?? data.bookings));
602
+ cursor = text(object(data.pagination).nextCursor, 500);
603
+ if (!cursor) break;
604
+ }
605
+ return bookings.flatMap((booking) => {
606
+ const uid = text(booking.uid, 200) ?? (booking.id !== void 0 ? String(booking.id) : null);
607
+ const occurredAt = isoTime(booking.createdAt);
608
+ if (!uid || !occurredAt) return [];
609
+ const attendee = list(booking.attendees)[0] ?? {};
610
+ return [{
611
+ key: `booking:${uid}`,
612
+ occurredAt,
613
+ person: person({
614
+ email: attendee.email,
615
+ name: attendee.name,
616
+ company: null
617
+ }),
618
+ summary: `Booked "${text(booking.title, 200) ?? "a meeting"}" for ${text(booking.start ?? booking.startTime, 40) ?? "a future time"}`,
619
+ record: {
620
+ bookingUid: uid,
621
+ start: text(booking.start ?? booking.startTime, 40),
622
+ end: text(booking.end ?? booking.endTime, 40),
623
+ description: text(booking.description, 2e3),
624
+ eventTypeId: booking.eventTypeId ?? object(booking.eventType).id ?? null
625
+ }
626
+ }];
627
+ });
628
+ }
629
+ },
630
+ {
631
+ provider: "intercom",
632
+ event: "intercom.conversation.created",
633
+ description: "A customer or lead started a new conversation.",
634
+ intervalSeconds: POLL_SECONDS,
635
+ reads: ["tickets.search"],
636
+ async list({ read, since }) {
637
+ return list(object(await read("tickets.search", { body: {
638
+ query: {
639
+ field: "created_at",
640
+ operator: ">",
641
+ value: seconds(since) - 1
642
+ },
643
+ pagination: { per_page: 50 }
644
+ } })).conversations).flatMap((conversation) => {
645
+ const id = text(conversation.id, 100);
646
+ const occurredAt = isoTime(conversation.created_at);
647
+ if (!id || !occurredAt) return [];
648
+ const source = object(conversation.source);
649
+ const author = object(source.author);
650
+ if (author.type === "admin" || author.type === "bot") return [];
651
+ const body = text(String(source.body ?? "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " "), 2e3);
652
+ return [{
653
+ key: `conversation:${id}`,
654
+ occurredAt,
655
+ person: person({
656
+ email: author.email,
657
+ name: author.name,
658
+ company: null
659
+ }),
660
+ summary: `New Intercom conversation${text(source.subject, 200) ? `: ${text(source.subject, 200)}` : ""}`,
661
+ record: {
662
+ conversationId: id,
663
+ subject: text(source.subject, 300),
664
+ body,
665
+ deliveredAs: text(source.delivered_as, 50)
666
+ }
667
+ }];
668
+ });
669
+ }
670
+ }
671
+ ];
672
+ //#endregion
673
+ //#region src/triggers/index.ts
674
+ const MAX_EVENTS_PER_POLL = 50;
675
+ /** Poll one subscription once. `cursor` is null on the first poll after it is created. */
676
+ async function pollHostedTrigger(trigger, context) {
677
+ if (!context.cursor) return {
678
+ events: [],
679
+ cursor: {
680
+ since: context.now.toISOString(),
681
+ seen: []
682
+ },
683
+ more: false
684
+ };
685
+ const cursor = context.cursor;
686
+ const sinceMs = Date.parse(cursor.since);
687
+ const seen = new Set(cursor.seen);
688
+ const fresh = (await trigger.list({
689
+ read: context.read,
690
+ since: cursor.since,
691
+ now: context.now
692
+ })).filter((record) => !seen.has(record.key) && Number.isFinite(Date.parse(record.occurredAt)) && Date.parse(record.occurredAt) >= sinceMs).sort((a, b) => Date.parse(a.occurredAt) - Date.parse(b.occurredAt));
693
+ const emitted = fresh.slice(0, 50);
694
+ const since = emitted.reduce((latest, record) => Date.parse(record.occurredAt) > Date.parse(latest) ? new Date(Date.parse(record.occurredAt)).toISOString() : latest, cursor.since);
695
+ const keys = [...cursor.seen.filter((key) => !emitted.some((record) => record.key === key)), ...emitted.map((record) => record.key)];
696
+ return {
697
+ events: emitted.map((record) => ({
698
+ id: `${trigger.provider}:${record.key}`,
699
+ type: trigger.event,
700
+ occurredAt: new Date(Date.parse(record.occurredAt)).toISOString(),
701
+ person: record.person,
702
+ summary: record.summary,
703
+ record: record.record
704
+ })),
705
+ cursor: {
706
+ since,
707
+ seen: keys.slice(-200)
708
+ },
709
+ more: fresh.length > emitted.length
710
+ };
711
+ }
712
+ function hostedTrigger(provider, event) {
713
+ return HOSTED_TRIGGER_DEFINITIONS.find((trigger) => trigger.provider === provider && trigger.event === event);
714
+ }
715
+ /** Providers with at least one hosted trigger. */
716
+ const HOSTED_TRIGGER_PROVIDER_IDS = [...new Set(HOSTED_TRIGGER_DEFINITIONS.map((trigger) => trigger.provider))];
717
+ /** The closed catalog of a provider's hosted trigger events, for validating `on.provider_event.event`. */
718
+ function hostedTriggerEventCatalog(provider) {
719
+ const events = HOSTED_TRIGGER_DEFINITIONS.filter((trigger) => trigger.provider === provider);
720
+ if (events.length === 0) return void 0;
721
+ return {
722
+ namespace: `${provider}.`,
723
+ closed: true,
724
+ events: events.map((trigger) => ({ id: trigger.event }))
725
+ };
726
+ }
727
+ //#endregion
728
+ export { HOSTED_TRIGGER_DEFINITIONS, HOSTED_TRIGGER_PROVIDER_IDS, MAX_EVENTS_PER_POLL, hostedTrigger, hostedTriggerEventCatalog, pollHostedTrigger };
729
+
730
+ //# sourceMappingURL=index.js.map