@slates/provider-handler 1.0.0-rc.3 → 1.0.0-rc.32

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 (71) hide show
  1. package/dist/index.cjs +1475 -2
  2. package/dist/index.d.cts +8 -0
  3. package/dist/index.d.ts +6 -3
  4. package/dist/index.module.js +1452 -2
  5. package/package.json +11 -10
  6. package/src/attachments.test.ts +171 -0
  7. package/src/index.ts +1060 -137
  8. package/src/pQueue.test.ts +18 -0
  9. package/src/pQueue.ts +18 -0
  10. package/src/spec.test.ts +83 -0
  11. package/src/spec.ts +150 -21
  12. package/src/validation.ts +1 -1
  13. package/src/webhook.test.ts +65 -0
  14. package/src/webhook.ts +69 -0
  15. package/dist/action/action.d.ts +0 -90
  16. package/dist/action/action.d.ts.map +0 -1
  17. package/dist/action/builder.d.ts +0 -27
  18. package/dist/action/builder.d.ts.map +0 -1
  19. package/dist/action/index.d.ts +0 -5
  20. package/dist/action/index.d.ts.map +0 -1
  21. package/dist/action/tool.d.ts +0 -11
  22. package/dist/action/tool.d.ts.map +0 -1
  23. package/dist/action/trigger.d.ts +0 -14
  24. package/dist/action/trigger.d.ts.map +0 -1
  25. package/dist/auth/auth.d.ts +0 -26
  26. package/dist/auth/auth.d.ts.map +0 -1
  27. package/dist/auth/index.d.ts +0 -3
  28. package/dist/auth/index.d.ts.map +0 -1
  29. package/dist/auth/types.d.ts +0 -148
  30. package/dist/auth/types.d.ts.map +0 -1
  31. package/dist/axios/index.d.ts +0 -4
  32. package/dist/axios/index.d.ts.map +0 -1
  33. package/dist/config/config.d.ts +0 -22
  34. package/dist/config/config.d.ts.map +0 -1
  35. package/dist/config/index.d.ts +0 -2
  36. package/dist/config/index.d.ts.map +0 -1
  37. package/dist/context/context.d.ts +0 -20
  38. package/dist/context/context.d.ts.map +0 -1
  39. package/dist/context/hook.d.ts +0 -4
  40. package/dist/context/hook.d.ts.map +0 -1
  41. package/dist/context/index.d.ts +0 -2
  42. package/dist/context/index.d.ts.map +0 -1
  43. package/dist/error/base.d.ts +0 -5
  44. package/dist/error/base.d.ts.map +0 -1
  45. package/dist/error/declaration.d.ts +0 -6
  46. package/dist/error/declaration.d.ts.map +0 -1
  47. package/dist/error/index.d.ts +0 -3
  48. package/dist/error/index.d.ts.map +0 -1
  49. package/dist/index.cjs.map +0 -1
  50. package/dist/index.d.ts.map +0 -1
  51. package/dist/index.modern.js +0 -2
  52. package/dist/index.modern.js.map +0 -1
  53. package/dist/index.module.js.map +0 -1
  54. package/dist/index.umd.js +0 -2
  55. package/dist/index.umd.js.map +0 -1
  56. package/dist/spec.d.ts +0 -14
  57. package/dist/spec.d.ts.map +0 -1
  58. package/dist/specification/index.d.ts +0 -3
  59. package/dist/specification/index.d.ts.map +0 -1
  60. package/dist/specification/slate.d.ts +0 -15
  61. package/dist/specification/slate.d.ts.map +0 -1
  62. package/dist/specification/specification.d.ts +0 -30
  63. package/dist/specification/specification.d.ts.map +0 -1
  64. package/dist/specification/zero.d.ts +0 -13
  65. package/dist/specification/zero.d.ts.map +0 -1
  66. package/dist/state.d.ts +0 -8
  67. package/dist/state.d.ts.map +0 -1
  68. package/dist/tokens.d.ts +0 -34
  69. package/dist/tokens.d.ts.map +0 -1
  70. package/dist/validation.d.ts +0 -5
  71. package/dist/validation.d.ts.map +0 -1
package/src/index.ts CHANGED
@@ -1,9 +1,181 @@
1
1
  import { badRequestError, preconditionFailedError, ServiceError } from '@lowerdeck/error';
2
- import { createSlatesProviderProtoHandler, SlatesParticipant } from '@slates/proto';
3
- import { Slate, SlateContext, SlateLogger, SlateLogListener } from '@slates/provider';
4
- import { getAction, getActionWithType, getAuthMethod, mapAction, mapAuthMethod } from './spec';
2
+ import {
3
+ createSlatesProviderProtoHandler,
4
+ SLATES_PROTOCOL_VERSION,
5
+ type SlatesParticipant
6
+ } from '@slates/proto';
7
+ import {
8
+ redactUrlAttachmentSecrets,
9
+ runWithContext,
10
+ type Slate,
11
+ type SlateAttachment,
12
+ SlateContext,
13
+ type SlateLiveInvocationInfo,
14
+ SlateLogger,
15
+ type SlateLogListener,
16
+ SlatePublicContext,
17
+ uploadAttachmentDirect
18
+ } from '@slates/provider';
19
+ import { PQueue } from './pQueue';
20
+ import {
21
+ evaluateTriggerMatches,
22
+ getActionWithType,
23
+ getAdapter,
24
+ getAuthMethod,
25
+ getMappableAction,
26
+ getTriggerGroup,
27
+ getWebhookAutoRegistration,
28
+ getWebhookManualRegistration,
29
+ isMappableTrigger,
30
+ mapAction,
31
+ mapAdapter,
32
+ mapAuthMethod,
33
+ mapTriggerGroup
34
+ } from './spec';
5
35
  import { State } from './state';
6
36
  import { toJsonSchema, validate } from './validation';
37
+ import { serializeWebhookHttpResponse } from './webhook';
38
+
39
+ let DEFAULT_MAX_ATTACHMENT_SIZE_BYTES = 100_000_000;
40
+
41
+ let routeAttachmentsThroughDirectUpload = async (
42
+ attachments: SlateAttachment[] | undefined,
43
+ live: SlateLiveInvocationInfo | null
44
+ ): Promise<SlateAttachment[] | undefined> => {
45
+ if (!attachments || attachments.length === 0 || !live) return attachments;
46
+
47
+ let contentEntries = attachments
48
+ .map((attachment, index) => ({ attachment, index }))
49
+ .filter(entry => entry.attachment.content.type === 'content');
50
+ if (contentEntries.length === 0) return attachments;
51
+
52
+ let queue = new PQueue({ concurrency: 10 });
53
+ let replacements = new Map<number, SlateAttachment | null>();
54
+
55
+ await Promise.all(
56
+ contentEntries.map(entry =>
57
+ queue.add(async () => {
58
+ try {
59
+ let body =
60
+ entry.attachment.content.type === 'content'
61
+ ? entry.attachment.content.encoding === 'base64'
62
+ ? Buffer.from(entry.attachment.content.content, 'base64')
63
+ : Buffer.from(entry.attachment.content.content, 'utf-8')
64
+ : Buffer.alloc(0);
65
+
66
+ replacements.set(
67
+ entry.index,
68
+ await uploadAttachmentDirect({
69
+ live,
70
+ mimeType: entry.attachment.mimeType,
71
+ body
72
+ })
73
+ );
74
+ } catch {
75
+ // Upload failed or was interrupted -- we simply don't have this attachment, not a
76
+ // failed tool call.
77
+ replacements.set(entry.index, null);
78
+ }
79
+ })
80
+ )
81
+ );
82
+
83
+ return attachments
84
+ .map((attachment, index) =>
85
+ replacements.has(index) ? replacements.get(index) : attachment
86
+ )
87
+ .filter((a): a is SlateAttachment => a != null);
88
+ };
89
+
90
+ let isRecord = (value: unknown): value is Record<string, unknown> =>
91
+ typeof value === 'object' && value !== null && !Array.isArray(value);
92
+
93
+ let getObjectKeyCount = (value: unknown) =>
94
+ isRecord(value) ? Object.keys(value).length : undefined;
95
+
96
+ let DOWNLOAD_ATTACHMENT_URL_KEYS = new Set([
97
+ 'downloadUrl',
98
+ 'fileUrl',
99
+ 'temporaryDownloadUrl',
100
+ 'webContentLink'
101
+ ]);
102
+
103
+ let isAttachmentUrl = (value: string) => /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
104
+
105
+ let collectOutputUrlAttachments = (
106
+ value: unknown,
107
+ seen = new Set<string>()
108
+ ): SlateAttachment[] => {
109
+ if (Array.isArray(value)) {
110
+ return value.flatMap(item => collectOutputUrlAttachments(item, seen));
111
+ }
112
+
113
+ if (!isRecord(value)) {
114
+ return [];
115
+ }
116
+
117
+ let attachments: SlateAttachment[] = [];
118
+
119
+ for (let [key, nestedValue] of Object.entries(value)) {
120
+ if (
121
+ DOWNLOAD_ATTACHMENT_URL_KEYS.has(key) &&
122
+ typeof nestedValue === 'string' &&
123
+ nestedValue.length > 0 &&
124
+ isAttachmentUrl(nestedValue) &&
125
+ !seen.has(nestedValue)
126
+ ) {
127
+ seen.add(nestedValue);
128
+ attachments.push({
129
+ content: {
130
+ type: 'url',
131
+ url: nestedValue
132
+ }
133
+ });
134
+ continue;
135
+ }
136
+
137
+ attachments.push(...collectOutputUrlAttachments(nestedValue, seen));
138
+ }
139
+
140
+ return attachments;
141
+ };
142
+
143
+ let mergeAttachments = (
144
+ explicitAttachments: SlateAttachment[] | undefined,
145
+ output: unknown
146
+ ): SlateAttachment[] | undefined => {
147
+ let attachments = [...(explicitAttachments ?? [])];
148
+ let seen = new Set(attachments.map(attachment => JSON.stringify(attachment)));
149
+
150
+ for (let attachment of collectOutputUrlAttachments(output)) {
151
+ let key = JSON.stringify(attachment);
152
+ if (seen.has(key)) continue;
153
+ seen.add(key);
154
+ attachments.push(attachment);
155
+ }
156
+
157
+ return attachments.length > 0 ? attachments : undefined;
158
+ };
159
+
160
+ let toErrorMetadata = (error: unknown) => {
161
+ if (error instanceof Error) {
162
+ return {
163
+ errorName: error.name,
164
+ errorMessage: error.message,
165
+ errorStack: error.stack
166
+ };
167
+ }
168
+
169
+ return {
170
+ errorValue: String(error)
171
+ };
172
+ };
173
+
174
+ let formatEntityLabel = (name: string, key: string) => `"${name}" (${key})`;
175
+ let resolveTraceMessage = <ResultType>(
176
+ message: string | ((result: ResultType) => string),
177
+ result: ResultType
178
+ ) => (typeof message === 'function' ? message(result) : message);
7
179
 
8
180
  export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
9
181
  slate: Slate<ConfigType, AuthType>,
@@ -15,10 +187,92 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
15
187
 
16
188
  let auth = new State<{ authenticationMethodId: string; output: AuthType } | null>(null);
17
189
  let config = new State<{ value: ConfigType } | null>(null);
18
-
19
190
  let session = new State<{ id: string; state: any } | null>(null);
20
191
 
192
+ let hubCapabilities = new State<{
193
+ attachments?: { directUpload?: { enabled: boolean; maxAttachmentSizeBytes?: number } };
194
+ triggers?: boolean;
195
+ } | null>(null);
196
+ let liveInvocation = new State<Pick<SlateLiveInvocationInfo, 'token' | 'baseUrl'> | null>(
197
+ null
198
+ );
199
+
200
+ let getLiveInvocation = (): SlateLiveInvocationInfo | null => {
201
+ let directUpload = hubCapabilities.get()?.attachments?.directUpload;
202
+ let live = liveInvocation.get();
203
+
204
+ if (!directUpload?.enabled || !live) return null;
205
+
206
+ return {
207
+ ...live,
208
+ maxAttachmentSizeBytes:
209
+ directUpload.maxAttachmentSizeBytes ?? DEFAULT_MAX_ATTACHMENT_SIZE_BYTES
210
+ };
211
+ };
212
+
213
+ let supportsTriggerGroups = () => !!hubCapabilities.get()?.triggers;
214
+
21
215
  let logger = new SlateLogger(listeners);
216
+ let providerTrace = {
217
+ providerId: slate.spec.key,
218
+ providerName: slate.spec.name
219
+ };
220
+
221
+ let traceProviderCall = async <ResultType>(
222
+ trace: {
223
+ component: 'config' | 'auth' | 'action';
224
+ functionName: string;
225
+ message: string;
226
+ successMessage: string | ((result: ResultType) => string);
227
+ errorMessage?: string;
228
+ metadata?: Record<string, unknown>;
229
+ onSuccess?: (result: ResultType) => Record<string, unknown> | undefined;
230
+ },
231
+ handler: () => Promise<ResultType>
232
+ ): Promise<ResultType> => {
233
+ let startedAt = Date.now();
234
+
235
+ logger.info({
236
+ ...providerTrace,
237
+ ...trace.metadata,
238
+ component: trace.component,
239
+ functionName: trace.functionName,
240
+ phase: 'start',
241
+ message: trace.message
242
+ });
243
+
244
+ try {
245
+ let result = await handler();
246
+ let successMessage = resolveTraceMessage(trace.successMessage, result);
247
+
248
+ logger.info({
249
+ ...providerTrace,
250
+ ...trace.metadata,
251
+ ...(trace.onSuccess?.(result) ?? {}),
252
+ component: trace.component,
253
+ functionName: trace.functionName,
254
+ phase: 'success',
255
+ durationMs: Date.now() - startedAt,
256
+ message: successMessage
257
+ });
258
+
259
+ return result;
260
+ } catch (error) {
261
+ logger.error({
262
+ ...providerTrace,
263
+ ...trace.metadata,
264
+ ...toErrorMetadata(error),
265
+ component: trace.component,
266
+ functionName: trace.functionName,
267
+ phase: 'error',
268
+ durationMs: Date.now() - startedAt,
269
+ message:
270
+ trace.errorMessage ??
271
+ `${typeof trace.successMessage === 'string' ? trace.successMessage : trace.message} failed`
272
+ });
273
+ throw error;
274
+ }
275
+ };
22
276
 
23
277
  let getContextBasic = () => {
24
278
  let currentProtocol = protocol.get();
@@ -65,6 +319,19 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
65
319
  };
66
320
  };
67
321
 
322
+ let getAuthConfig = (): Record<string, any> => config.get()?.value ?? {};
323
+
324
+ let getEmptyContext = () => new SlateContext({}, {}, {}, slate.spec as any, logger);
325
+ let getAuthContext = () =>
326
+ new SlateContext(getAuthConfig(), {}, {}, slate.spec as any, logger);
327
+ let withRequestTraces = <Result extends Record<string, any>>(
328
+ context: SlatePublicContext<any>,
329
+ result: Result
330
+ ) => {
331
+ let requestTraces = context.getHttpTraces();
332
+ return requestTraces.length > 0 ? { ...result, requestTraces } : result;
333
+ };
334
+
68
335
  manager.onNotification('slates/hello', async ({ params }) => {
69
336
  protocol.set(params.protocol);
70
337
  });
@@ -118,6 +385,31 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
118
385
  });
119
386
  });
120
387
 
388
+ manager.onNotification('slates/hub.capabilities.set', async ({ params }) => {
389
+ hubCapabilities.set(params.capabilities);
390
+ });
391
+
392
+ manager.onNotification('slates/hub.live_invocation.set', async ({ params }) => {
393
+ liveInvocation.set({
394
+ token: params.token,
395
+ baseUrl: params.baseUrl
396
+ });
397
+ });
398
+
399
+ manager.onRequest('slates/provider.capabilities.get', async () => ({
400
+ capabilities: {
401
+ hub: {
402
+ capabilitiesNotification: true,
403
+ liveInvocation: true
404
+ },
405
+ provider: {
406
+ triggerGroups:
407
+ (slate.triggerGroups?.length ?? 0) > 0 &&
408
+ (slate.actions ?? []).every(isMappableTrigger)
409
+ }
410
+ }
411
+ }));
412
+
121
413
  manager.onRequest('slates/config.changed', async ({ params }) => {
122
414
  getContextBasic();
123
415
 
@@ -133,15 +425,37 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
133
425
  return { success: true, config: newConfig };
134
426
  }
135
427
 
136
- let updatedConfig = await configChanged({
137
- previousConfig: params.previousConfig as ConfigType | null,
138
- newConfig
139
- });
428
+ let context = getEmptyContext();
429
+ let updatedConfig = await traceProviderCall<{ config?: ConfigType } | undefined>(
430
+ {
431
+ component: 'config',
432
+ functionName: 'configChanged',
433
+ message: 'Running config change handler',
434
+ successMessage: 'Config change handler completed',
435
+ metadata: {
436
+ hasPreviousConfig: params.previousConfig !== null,
437
+ newConfigKeyCount: getObjectKeyCount(newConfig)
438
+ },
439
+ onSuccess: result => ({
440
+ returnedConfig: !!result?.config
441
+ })
442
+ },
443
+ () =>
444
+ runWithContext(context, async () =>
445
+ configChanged({
446
+ previousConfig: params.previousConfig as ConfigType | null,
447
+ newConfig
448
+ })
449
+ )
450
+ );
140
451
 
141
- return { success: true, config: updatedConfig?.config ?? newConfig };
452
+ return withRequestTraces(context, {
453
+ success: true,
454
+ config: (updatedConfig?.config ?? newConfig) as Record<string, any>
455
+ });
142
456
  });
143
457
 
144
- manager.onRequest('slates/config.get_default', async ({ params }) => {
458
+ manager.onRequest('slates/config.get_default', async () => {
145
459
  getContextBasic();
146
460
 
147
461
  let getDefaultConfig = slate.spec.config.handlers.getDefaultConfig;
@@ -149,32 +463,50 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
149
463
  return { config: null };
150
464
  }
151
465
 
152
- let defaultConfig = await getDefaultConfig();
153
- return { config: defaultConfig };
466
+ let context = getEmptyContext();
467
+ let defaultConfig = await traceProviderCall<ConfigType>(
468
+ {
469
+ component: 'config',
470
+ functionName: 'getDefaultConfig',
471
+ message: 'Getting default config',
472
+ successMessage: 'Default config retrieved',
473
+ onSuccess: result => ({
474
+ configKeyCount: getObjectKeyCount(result)
475
+ })
476
+ },
477
+ () => runWithContext(context, async () => getDefaultConfig())
478
+ );
479
+ return withRequestTraces(context, {
480
+ config: (defaultConfig ?? null) as Record<string, any> | null
481
+ });
154
482
  });
155
483
 
156
- manager.onRequest('slates/config.schema.get', async ({ params }) => {
484
+ manager.onRequest('slates/config.schema.get', async () => {
157
485
  getContextBasic();
158
486
 
159
- return { schema: toJsonSchema(slate.spec.configSchema) };
487
+ return {
488
+ schema: toJsonSchema(slate.spec.configSchema),
489
+ docs: slate.spec.config.docsReferences ?? []
490
+ };
160
491
  });
161
492
 
162
- manager.onRequest('slates/provider.identify', async ({ params }) => {
493
+ manager.onRequest('slates/provider.identify', async () => {
163
494
  getContextBasic();
164
495
 
165
496
  return {
166
- protocol: 'slates@2026-01-01',
497
+ protocol: SLATES_PROTOCOL_VERSION,
167
498
  provider: {
168
499
  type: 'provider',
169
500
  id: slate.spec.key,
170
501
  name: slate.spec.name,
171
502
  description: slate.spec.description,
172
503
  metadata: slate.spec.parameters.metadata
173
- }
504
+ },
505
+ docs: slate.spec.docs ?? []
174
506
  };
175
507
  });
176
508
 
177
- manager.onRequest('slates/auth.methods.list', async ({ params }) => {
509
+ manager.onRequest('slates/auth.methods.list', async () => {
178
510
  getContextBasic();
179
511
 
180
512
  return {
@@ -199,7 +531,25 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
199
531
  return { input: null };
200
532
  }
201
533
 
202
- return { input: await authMethod.getDefaultInput() };
534
+ let context = getEmptyContext();
535
+ let input = await traceProviderCall(
536
+ {
537
+ component: 'auth',
538
+ functionName: 'getDefaultInput',
539
+ message: 'Getting default authentication input',
540
+ successMessage: 'Default authentication input retrieved',
541
+ metadata: {
542
+ authenticationMethodId: params.authenticationMethodId,
543
+ authenticationMethodName: authMethod.name
544
+ },
545
+ onSuccess: result => ({
546
+ inputKeyCount: getObjectKeyCount(result)
547
+ })
548
+ },
549
+ () => runWithContext(context, () => authMethod.getDefaultInput!())
550
+ );
551
+
552
+ return withRequestTraces(context, { input });
203
553
  });
204
554
 
205
555
  manager.onRequest('slates/auth.input.changed', async ({ params }) => {
@@ -210,12 +560,36 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
210
560
  return { success: true, input: params.newInput };
211
561
  }
212
562
 
213
- let updatedInput = await authMethod.onInputChanged({
214
- previousInput: params.previousInput as any | null,
215
- newInput: params.newInput
216
- });
563
+ let context = getEmptyContext();
564
+ let updatedInput = await traceProviderCall(
565
+ {
566
+ component: 'auth',
567
+ functionName: 'onInputChanged',
568
+ message: 'Running authentication input change handler',
569
+ successMessage: 'Authentication input change handler completed',
570
+ metadata: {
571
+ authenticationMethodId: params.authenticationMethodId,
572
+ authenticationMethodName: authMethod.name,
573
+ hasPreviousInput: params.previousInput !== null,
574
+ newInputKeyCount: getObjectKeyCount(params.newInput)
575
+ },
576
+ onSuccess: result => ({
577
+ returnedInput: !!result?.input
578
+ })
579
+ },
580
+ () =>
581
+ runWithContext(context, () =>
582
+ authMethod.onInputChanged!({
583
+ previousInput: params.previousInput as any | null,
584
+ newInput: params.newInput
585
+ })
586
+ )
587
+ );
217
588
 
218
- return { success: true, input: updatedInput?.input ?? params.newInput };
589
+ return withRequestTraces(context, {
590
+ success: true,
591
+ input: updatedInput?.input ?? params.newInput
592
+ });
219
593
  });
220
594
 
221
595
  manager.onRequest('slates/auth.output.get', async ({ params }) => {
@@ -234,8 +608,35 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
234
608
  }
235
609
 
236
610
  if ('getOutput' in authMethod) {
237
- let outputRes = await authMethod.getOutput({ input });
238
- return { output: outputRes.output };
611
+ let context = getAuthContext();
612
+ let outputRes = await traceProviderCall(
613
+ {
614
+ component: 'auth',
615
+ functionName: 'getOutput',
616
+ message: 'Getting authentication output',
617
+ successMessage: 'Authentication output retrieved',
618
+ metadata: {
619
+ authenticationMethodId: params.authenticationMethodId,
620
+ authenticationMethodName: authMethod.name,
621
+ inputKeyCount: getObjectKeyCount(input)
622
+ },
623
+ onSuccess: result => ({
624
+ outputKeyCount: getObjectKeyCount(result.output),
625
+ scopeCount: result.scopes?.length ?? 0
626
+ })
627
+ },
628
+ () =>
629
+ runWithContext(context, () =>
630
+ authMethod.getOutput({
631
+ input,
632
+ config: getAuthConfig()
633
+ })
634
+ )
635
+ );
636
+ return withRequestTraces(context, {
637
+ output: outputRes.output,
638
+ scopes: outputRes.scopes
639
+ });
239
640
  }
240
641
 
241
642
  return { output: input as any };
@@ -246,20 +647,47 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
246
647
  let authMethod = getAuthMethod(slate, params.authenticationMethodId);
247
648
 
248
649
  if ('handleCallback' in authMethod) {
249
- let callbackRes = await authMethod.handleCallback({
250
- code: params.code,
251
- state: params.state,
252
- redirectUri: params.redirectUri,
253
- input: params.input,
254
- clientId: params.clientId,
255
- clientSecret: params.clientSecret,
256
- scopes: params.scopes
257
- });
650
+ let context = getEmptyContext();
651
+ let callbackRes = await traceProviderCall(
652
+ {
653
+ component: 'auth',
654
+ functionName: 'handleCallback',
655
+ message: 'Handling authentication callback',
656
+ successMessage: 'Authentication callback handled',
657
+ metadata: {
658
+ authenticationMethodId: params.authenticationMethodId,
659
+ authenticationMethodName: authMethod.name,
660
+ scopeCount: params.scopes.length,
661
+ hasCallbackState: !!params.callbackState
662
+ },
663
+ onSuccess: result => ({
664
+ outputKeyCount: getObjectKeyCount(result.output),
665
+ returnedInput: !!result.input,
666
+ returnedScopeCount: result.scopes?.length
667
+ })
668
+ },
669
+ () =>
670
+ runWithContext(context, () =>
671
+ authMethod.handleCallback({
672
+ code: params.code,
673
+ state: params.state,
674
+ redirectUri: params.redirectUri,
675
+ input: params.input,
676
+ clientId: params.clientId,
677
+ clientSecret: params.clientSecret,
678
+ scopes: params.scopes,
679
+ callbackParams: params.callbackParams || {},
680
+ callbackState: params.callbackState || {},
681
+ config: getAuthConfig()
682
+ })
683
+ )
684
+ );
258
685
 
259
- return {
686
+ return withRequestTraces(context, {
260
687
  output: callbackRes.output,
261
- input: callbackRes.input
262
- };
688
+ input: callbackRes.input,
689
+ scopes: callbackRes.scopes
690
+ });
263
691
  }
264
692
 
265
693
  throw new ServiceError(
@@ -274,19 +702,43 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
274
702
  let authMethod = getAuthMethod(slate, params.authenticationMethodId);
275
703
 
276
704
  if ('getAuthorizationUrl' in authMethod) {
277
- let urlRes = await authMethod.getAuthorizationUrl({
278
- redirectUri: params.redirectUri,
279
- state: params.state,
280
- input: params.input,
281
- clientId: params.clientId,
282
- clientSecret: params.clientSecret,
283
- scopes: params.scopes
284
- });
705
+ let context = getEmptyContext();
706
+ let urlRes = await traceProviderCall(
707
+ {
708
+ component: 'auth',
709
+ functionName: 'getAuthorizationUrl',
710
+ message: 'Getting authentication authorization URL',
711
+ successMessage: 'Authentication authorization URL retrieved',
712
+ metadata: {
713
+ authenticationMethodId: params.authenticationMethodId,
714
+ authenticationMethodName: authMethod.name,
715
+ scopeCount: params.scopes.length,
716
+ inputKeyCount: getObjectKeyCount(params.input)
717
+ },
718
+ onSuccess: result => ({
719
+ returnedInput: !!result.input,
720
+ hasCallbackState: !!result.callbackState
721
+ })
722
+ },
723
+ () =>
724
+ runWithContext(context, () =>
725
+ authMethod.getAuthorizationUrl({
726
+ redirectUri: params.redirectUri,
727
+ state: params.state,
728
+ input: params.input,
729
+ clientId: params.clientId,
730
+ clientSecret: params.clientSecret,
731
+ scopes: params.scopes,
732
+ config: getAuthConfig()
733
+ })
734
+ )
735
+ );
285
736
 
286
- return {
737
+ return withRequestTraces(context, {
287
738
  authorizationUrl: urlRes.url,
288
- input: urlRes.input
289
- };
739
+ input: urlRes.input,
740
+ callbackState: urlRes.callbackState
741
+ });
290
742
  }
291
743
 
292
744
  throw new ServiceError(
@@ -301,15 +753,47 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
301
753
  let authMethod = getAuthMethod(slate, params.authenticationMethodId);
302
754
 
303
755
  if (authMethod.getProfile) {
304
- let profileRes = await authMethod.getProfile({
305
- output: params.output as any,
306
- input: params.input,
307
- scopes: params.scopes
308
- });
756
+ let context = getAuthContext();
757
+ let profileRes = await traceProviderCall(
758
+ {
759
+ component: 'auth',
760
+ functionName: 'getProfile',
761
+ message: 'Getting authentication profile',
762
+ successMessage: 'Authentication profile retrieved',
763
+ metadata: {
764
+ authenticationMethodId: params.authenticationMethodId,
765
+ authenticationMethodName: authMethod.name,
766
+ scopeCount: params.scopes.length,
767
+ inputKeyCount: getObjectKeyCount(params.input),
768
+ outputKeyCount: getObjectKeyCount(params.output)
769
+ },
770
+ onSuccess: result => ({
771
+ profileKeyCount: getObjectKeyCount(result.profile)
772
+ })
773
+ },
774
+ () =>
775
+ runWithContext(context, () => {
776
+ if (authMethod.type === 'auth.oauth') {
777
+ return authMethod.getProfile!({
778
+ output: params.output as any,
779
+ input: params.input,
780
+ scopes: params.scopes,
781
+ config: getAuthConfig()
782
+ });
783
+ }
784
+
785
+ return authMethod.getProfile!({
786
+ output: params.output as any,
787
+ input: params.input,
788
+ scopes: params.scopes,
789
+ config: getAuthConfig()
790
+ })!;
791
+ })
792
+ );
309
793
 
310
- return {
794
+ return withRequestTraces(context, {
311
795
  profile: profileRes.profile
312
- };
796
+ });
313
797
  }
314
798
 
315
799
  throw new ServiceError(
@@ -324,18 +808,53 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
324
808
  let authMethod = getAuthMethod(slate, params.authenticationMethodId);
325
809
 
326
810
  if ('handleTokenRefresh' in authMethod && authMethod.handleTokenRefresh) {
327
- let refreshRes = await authMethod.handleTokenRefresh({
328
- output: params.output as any,
329
- input: params.input,
330
- clientId: params.clientId,
331
- clientSecret: params.clientSecret,
332
- scopes: params.scopes
333
- });
811
+ let context = getAuthContext();
812
+ let refreshRes = await traceProviderCall(
813
+ {
814
+ component: 'auth',
815
+ functionName: 'handleTokenRefresh',
816
+ message: 'Refreshing authentication token',
817
+ successMessage: 'Authentication token refreshed',
818
+ metadata: {
819
+ authenticationMethodId: params.authenticationMethodId,
820
+ authenticationMethodName: authMethod.name,
821
+ scopeCount: params.scopes.length,
822
+ inputKeyCount: getObjectKeyCount(params.input),
823
+ outputKeyCount: getObjectKeyCount(params.output)
824
+ },
825
+ onSuccess: result => ({
826
+ refreshedOutputKeyCount: getObjectKeyCount(result.output),
827
+ returnedInput: !!result.input
828
+ })
829
+ },
830
+ () =>
831
+ runWithContext(context, () => {
832
+ if (authMethod.type === 'auth.oauth') {
833
+ return authMethod.handleTokenRefresh!({
834
+ output: params.output as any,
835
+ input: params.input,
836
+ clientId: params.clientId,
837
+ clientSecret: params.clientSecret,
838
+ scopes: params.scopes,
839
+ config: getAuthConfig()
840
+ });
841
+ }
842
+
843
+ return authMethod.handleTokenRefresh!({
844
+ output: params.output as any,
845
+ input: params.input,
846
+ clientId: params.clientId,
847
+ clientSecret: params.clientSecret,
848
+ scopes: params.scopes,
849
+ config: getAuthConfig()
850
+ });
851
+ })
852
+ );
334
853
 
335
- return {
854
+ return withRequestTraces(context, {
336
855
  output: refreshRes.output,
337
856
  input: refreshRes.input
338
- };
857
+ });
339
858
  }
340
859
 
341
860
  throw new ServiceError(
@@ -348,22 +867,65 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
348
867
  manager.onRequest('slates/actions.list', async ({ params }) => {
349
868
  getContextBasic();
350
869
 
870
+ let actions = params.includeAdapterActions
871
+ ? slate.actions
872
+ : slate.actions.filter(action => !action.adapter);
873
+
874
+ actions = actions.filter(isMappableTrigger);
875
+
876
+ if (!supportsTriggerGroups()) {
877
+ actions = actions.filter(action => action.type !== 'trigger');
878
+ }
879
+
880
+ return {
881
+ actions: actions.map(a => mapAction(slate, a))
882
+ };
883
+ });
884
+
885
+ manager.onRequest('slates/adapters.list', async () => {
886
+ getContextBasic();
887
+
888
+ return {
889
+ adapters: slate.adapters.map(mapAdapter)
890
+ };
891
+ });
892
+
893
+ manager.onRequest('slates/adapter.get', async ({ params }) => {
894
+ getContextBasic();
895
+ let adapter = getAdapter(slate, params.adapterId);
896
+
351
897
  return {
352
- actions: slate.actions.map(a => mapAction(slate, a))
898
+ adapter: mapAdapter(adapter)
353
899
  };
354
900
  });
355
901
 
356
902
  manager.onRequest('slates/action.get', async ({ params }) => {
357
903
  getContextBasic();
358
- let action = getAction(slate, params.actionId);
904
+ let action = getMappableAction(slate, params.actionId);
359
905
 
360
906
  return {
361
907
  action: mapAction(slate, action)
362
908
  };
363
909
  });
364
910
 
911
+ manager.onRequest('slates/trigger_groups.list', async () => {
912
+ getContextBasic();
913
+
914
+ return {
915
+ triggerGroups: slate.triggerGroups.map(mapTriggerGroup)
916
+ };
917
+ });
918
+
919
+ manager.onRequest('slates/trigger_group.get', async ({ params }) => {
920
+ getContextBasic();
921
+ let triggerGroup = getTriggerGroup(slate, params.triggerGroupId);
922
+
923
+ return {
924
+ triggerGroup: mapTriggerGroup(triggerGroup)
925
+ };
926
+ });
927
+
365
928
  manager.onRequest('slates/action.tool.invoke', async ({ params }) => {
366
- let ctx = getContextFull();
367
929
  let action = getActionWithType(slate, 'tool', params.actionId);
368
930
 
369
931
  let input = validate(
@@ -373,11 +935,69 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
373
935
  `Invalid input for tool ID: ${params.actionId}`
374
936
  );
375
937
 
376
- let res = await action.handleInvocation(
377
- new SlateContext(ctx.config, input, ctx.auth?.output!, slate.spec, logger)
378
- );
938
+ let invoke = async (context: SlatePublicContext<any>) => {
939
+ let res = await traceProviderCall(
940
+ {
941
+ component: 'action',
942
+ functionName: 'handleInvocation',
943
+ message: `Starting tool ${formatEntityLabel(action.name, action.key)}`,
944
+ successMessage: `Completed tool ${formatEntityLabel(action.name, action.key)}`,
945
+ errorMessage: `Tool ${formatEntityLabel(action.name, action.key)} failed`,
946
+ metadata: {
947
+ actionId: action.key,
948
+ actionName: action.name,
949
+ actionType: action.type,
950
+ isPublic: action.isPublic,
951
+ inputKeyCount: getObjectKeyCount(input)
952
+ },
953
+ onSuccess: result => ({
954
+ hasMessage: !!result.message,
955
+ actionResultMessage: result.message,
956
+ outputKeyCount: getObjectKeyCount(result.output),
957
+ attachmentCount: result.attachments?.length
958
+ })
959
+ },
960
+ () => runWithContext(context, () => action.handleInvocation(context as any))
961
+ );
379
962
 
380
- return { output: res.output, message: res.message };
963
+ let contextAttachments = await context._finalizeAttachments();
964
+ let merged = mergeAttachments(
965
+ [...(res.attachments ?? []), ...contextAttachments],
966
+ res.output
967
+ );
968
+
969
+ let redacted = redactUrlAttachmentSecrets(
970
+ merged,
971
+ context._getAuthConfigForRedaction()
972
+ );
973
+ let finalAttachments = await routeAttachmentsThroughDirectUpload(
974
+ redacted,
975
+ getLiveInvocation()
976
+ );
977
+
978
+ return withRequestTraces(context, {
979
+ output: res.output,
980
+ message: res.message,
981
+ attachments: finalAttachments
982
+ });
983
+ };
984
+
985
+ if (action.isPublic) {
986
+ getContextBasic();
987
+ return invoke(new SlatePublicContext(input, slate.spec, logger, getLiveInvocation()));
988
+ }
989
+
990
+ let ctx = getContextFull();
991
+ return invoke(
992
+ new SlateContext(
993
+ ctx.config,
994
+ input,
995
+ ctx.auth?.output!,
996
+ slate.spec,
997
+ logger,
998
+ getLiveInvocation()
999
+ )
1000
+ );
381
1001
  });
382
1002
 
383
1003
  manager.onRequest('slates/action.trigger.map_event', async ({ params }) => {
@@ -391,46 +1011,282 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
391
1011
  `Invalid event for trigger ID: ${params.actionId}`
392
1012
  );
393
1013
 
394
- let res = await action.handleEvent(
395
- new SlateContext(ctx.config, input, ctx.auth?.output!, slate.spec, logger)
1014
+ let context = new SlateContext(ctx.config, input, ctx.auth?.output!, slate.spec, logger);
1015
+ let res = await traceProviderCall(
1016
+ {
1017
+ component: 'action',
1018
+ functionName: 'handleEvent',
1019
+ message: `Mapping event for trigger ${formatEntityLabel(action.name, action.key)}`,
1020
+ successMessage: result =>
1021
+ `Mapped trigger event "${result.type}" for ${formatEntityLabel(action.name, action.key)}`,
1022
+ errorMessage: `Trigger ${formatEntityLabel(action.name, action.key)} failed while mapping an event`,
1023
+ metadata: {
1024
+ actionId: action.key,
1025
+ actionName: action.name,
1026
+ actionType: action.type,
1027
+ inputKeyCount: getObjectKeyCount(input)
1028
+ },
1029
+ onSuccess: result => ({
1030
+ eventType: result.type,
1031
+ hasEventId: !!result.id,
1032
+ outputKeyCount: getObjectKeyCount(result.output)
1033
+ })
1034
+ },
1035
+ () => runWithContext(context, () => action.map(context))
396
1036
  );
397
1037
 
398
- return { id: res.id, type: res.type, output: res.output };
1038
+ return withRequestTraces(context, { id: res.id, type: res.type, output: res.output });
399
1039
  });
400
1040
 
401
1041
  manager.onRequest('slates/action.trigger.poll_events', async ({ params }) => {
402
- let ctx = getContextFull();
403
- let action = getActionWithType(slate, 'trigger', params.actionId);
1042
+ getContextBasic();
404
1043
 
405
- if (!action.pollEvents) {
1044
+ if (supportsTriggerGroups()) {
406
1045
  throw new ServiceError(
407
1046
  badRequestError({
408
- message: `Trigger action does not support polling: ${params.actionId}`
1047
+ message: `Legacy trigger polling is disabled once trigger_group support is announced: ${params.actionId}`
409
1048
  })
410
1049
  );
411
1050
  }
412
1051
 
413
- let res = await action.pollEvents(
414
- new SlateContext(
415
- ctx.config,
416
- { state: params.state },
417
- ctx.auth?.output!,
418
- slate.spec,
419
- logger
420
- )
1052
+ return { inputs: [], updatedState: params.state };
1053
+ });
1054
+
1055
+ manager.onRequest('slates/action.trigger.webhook_handle', async ({ params }) => {
1056
+ getContextBasic();
1057
+
1058
+ if (supportsTriggerGroups()) {
1059
+ throw new ServiceError(
1060
+ badRequestError({
1061
+ message: `Legacy trigger webhook handling is disabled once trigger_group support is announced: ${params.actionId}`
1062
+ })
1063
+ );
1064
+ }
1065
+
1066
+ return { inputs: [], updatedState: params.state, response: null };
1067
+ });
1068
+
1069
+ manager.onRequest('slates/action.trigger.webhook_register', async ({ params }) => {
1070
+ getContextBasic();
1071
+
1072
+ if (supportsTriggerGroups()) {
1073
+ throw new ServiceError(
1074
+ badRequestError({
1075
+ message: `Legacy trigger webhook registration is disabled once trigger_group support is announced: ${params.actionId}`
1076
+ })
1077
+ );
1078
+ }
1079
+
1080
+ return { registrationDetails: null };
1081
+ });
1082
+
1083
+ manager.onRequest('slates/action.trigger.webhook_unregister', async ({ params }) => {
1084
+ getContextBasic();
1085
+
1086
+ if (supportsTriggerGroups()) {
1087
+ throw new ServiceError(
1088
+ badRequestError({
1089
+ message: `Legacy trigger webhook unregistration is disabled once trigger_group support is announced: ${params.actionId}`
1090
+ })
1091
+ );
1092
+ }
1093
+
1094
+ return {};
1095
+ });
1096
+
1097
+ manager.onRequest('slates/trigger_group.webhook.targets_list', async ({ params }) => {
1098
+ let ctx = getContextFull();
1099
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1100
+ let autoRegistration = getWebhookAutoRegistration(group);
1101
+
1102
+ let context = new SlateContext(
1103
+ ctx.config,
1104
+ { pageToken: params.pageToken ?? null },
1105
+ ctx.auth?.output!,
1106
+ slate.spec,
1107
+ logger
1108
+ );
1109
+ let res = await traceProviderCall(
1110
+ {
1111
+ component: 'action',
1112
+ functionName: 'webhookTargetList',
1113
+ message: `Listing webhook targets for trigger group ${formatEntityLabel(group.name, group.key)}`,
1114
+ successMessage: result =>
1115
+ `Listed ${result.targets.length} webhook target(s) for trigger group ${formatEntityLabel(group.name, group.key)}`,
1116
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while listing webhook targets`,
1117
+ metadata: {
1118
+ triggerGroupId: group.key,
1119
+ triggerGroupName: group.name
1120
+ },
1121
+ onSuccess: result => ({
1122
+ targetCount: result.targets.length,
1123
+ hasNextPageToken: result.nextPageToken !== null
1124
+ })
1125
+ },
1126
+ () => runWithContext(context, () => autoRegistration.webhookTargetList(context))
421
1127
  );
422
1128
 
423
- return { inputs: res.inputs, updatedState: res.updatedState };
1129
+ return withRequestTraces(context, {
1130
+ targets: res.targets,
1131
+ nextPageToken: res.nextPageToken ?? null
1132
+ });
424
1133
  });
425
1134
 
426
- manager.onRequest('slates/action.trigger.webhook_handle', async ({ params }) => {
1135
+ manager.onRequest('slates/trigger_group.webhook.register', async ({ params }) => {
427
1136
  let ctx = getContextFull();
428
- let action = getActionWithType(slate, 'trigger', params.actionId);
1137
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1138
+ let autoRegistration = getWebhookAutoRegistration(group);
1139
+
1140
+ let context = new SlateContext(
1141
+ ctx.config,
1142
+ {
1143
+ webhookTargetIdentifier: params.webhookTargetIdentifier,
1144
+ webhookTargetPayload: params.webhookTargetPayload,
1145
+ webhookUrl: params.webhookUrl
1146
+ },
1147
+ ctx.auth?.output!,
1148
+ slate.spec,
1149
+ logger
1150
+ );
1151
+ let res = await traceProviderCall(
1152
+ {
1153
+ component: 'action',
1154
+ functionName: 'webhookRegister',
1155
+ message: `Registering webhook for trigger group ${formatEntityLabel(group.name, group.key)}`,
1156
+ successMessage: `Registered webhook for trigger group ${formatEntityLabel(group.name, group.key)}`,
1157
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while registering a webhook`,
1158
+ metadata: {
1159
+ triggerGroupId: group.key,
1160
+ triggerGroupName: group.name,
1161
+ webhookTargetIdentifier: params.webhookTargetIdentifier
1162
+ }
1163
+ },
1164
+ () => runWithContext(context, () => autoRegistration.webhookRegister(context))
1165
+ );
1166
+
1167
+ return withRequestTraces(context, {
1168
+ webhookRegistrationIdentifier: res.webhookRegistrationIdentifier,
1169
+ webhookRegistrationPayload: res.webhookRegistrationPayload
1170
+ });
1171
+ });
1172
+
1173
+ manager.onRequest('slates/trigger_group.webhook.unregister', async ({ params }) => {
1174
+ let ctx = getContextFull();
1175
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1176
+ let autoRegistration = getWebhookAutoRegistration(group);
1177
+
1178
+ let context = new SlateContext(
1179
+ ctx.config,
1180
+ {
1181
+ webhookRegistrationIdentifier: params.webhookRegistrationIdentifier,
1182
+ webhookRegistrationPayload: params.webhookRegistrationPayload
1183
+ },
1184
+ ctx.auth?.output!,
1185
+ slate.spec,
1186
+ logger
1187
+ );
1188
+ await traceProviderCall(
1189
+ {
1190
+ component: 'action',
1191
+ functionName: 'webhookUnregister',
1192
+ message: `Unregistering webhook for trigger group ${formatEntityLabel(group.name, group.key)}`,
1193
+ successMessage: `Unregistered webhook for trigger group ${formatEntityLabel(group.name, group.key)}`,
1194
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while unregistering a webhook`,
1195
+ metadata: {
1196
+ triggerGroupId: group.key,
1197
+ triggerGroupName: group.name,
1198
+ webhookRegistrationIdentifier: params.webhookRegistrationIdentifier
1199
+ }
1200
+ },
1201
+ () => runWithContext(context, () => autoRegistration.webhookUnregister(context))
1202
+ );
429
1203
 
430
- if (!action.handleRequest) {
1204
+ return withRequestTraces(context, {});
1205
+ });
1206
+
1207
+ manager.onRequest('slates/trigger_group.webhook.manual_setup', async ({ params }) => {
1208
+ getContextBasic();
1209
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1210
+ let manualRegistration = getWebhookManualRegistration(group);
1211
+
1212
+ let context = new SlateContext(
1213
+ {},
1214
+ { webhookUrl: params.webhookUrl },
1215
+ {},
1216
+ slate.spec as any,
1217
+ logger
1218
+ );
1219
+ let res = await traceProviderCall(
1220
+ {
1221
+ component: 'action',
1222
+ functionName: 'webhookManualSetup',
1223
+ message: `Building manual webhook setup for trigger group ${formatEntityLabel(group.name, group.key)}`,
1224
+ successMessage: `Built manual webhook setup for trigger group ${formatEntityLabel(group.name, group.key)}`,
1225
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while building a manual webhook setup`,
1226
+ metadata: {
1227
+ triggerGroupId: group.key,
1228
+ triggerGroupName: group.name
1229
+ }
1230
+ },
1231
+ () => runWithContext(context, () => manualRegistration.setup(context))
1232
+ );
1233
+
1234
+ return {
1235
+ webhookSetupDocument: res.webhookSetupDocument,
1236
+ partialWebhookRegistrationPayload: res.partialWebhookRegistrationPayload
1237
+ };
1238
+ });
1239
+
1240
+ manager.onRequest('slates/trigger_group.webhook.manual_finish', async ({ params }) => {
1241
+ getContextBasic();
1242
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1243
+ let manualRegistration = getWebhookManualRegistration(group);
1244
+
1245
+ if (!manualRegistration.finish) {
1246
+ throw new ServiceError(
1247
+ badRequestError({
1248
+ message: `Trigger group does not support manual webhook finish: ${params.triggerGroupId}`
1249
+ })
1250
+ );
1251
+ }
1252
+
1253
+ let context = new SlateContext(
1254
+ {},
1255
+ {
1256
+ webhookUrl: params.webhookUrl,
1257
+ partialWebhookRegistrationPayload: params.partialWebhookRegistrationPayload,
1258
+ userWebhookRegistrationPayload: params.userWebhookRegistrationPayload
1259
+ },
1260
+ {},
1261
+ slate.spec as any,
1262
+ logger
1263
+ );
1264
+ let res = await traceProviderCall(
1265
+ {
1266
+ component: 'action',
1267
+ functionName: 'webhookManualFinish',
1268
+ message: `Finishing manual webhook setup for trigger group ${formatEntityLabel(group.name, group.key)}`,
1269
+ successMessage: `Finished manual webhook setup for trigger group ${formatEntityLabel(group.name, group.key)}`,
1270
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while finishing a manual webhook setup`,
1271
+ metadata: {
1272
+ triggerGroupId: group.key,
1273
+ triggerGroupName: group.name
1274
+ }
1275
+ },
1276
+ () => runWithContext(context, () => manualRegistration.finish!(context))
1277
+ );
1278
+
1279
+ return withRequestTraces(context, {
1280
+ webhookRegistrationPayload: res.webhookRegistrationPayload
1281
+ });
1282
+ });
1283
+
1284
+ manager.onRequest('slates/trigger_group.webhook.process', async ({ params }) => {
1285
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1286
+ if (group.source !== 'webhook' || !group.webhook) {
431
1287
  throw new ServiceError(
432
1288
  badRequestError({
433
- message: `Trigger action does not support webhook requests: ${params.actionId}`
1289
+ message: `Trigger group does not support webhook processing: ${params.triggerGroupId}`
434
1290
  })
435
1291
  );
436
1292
  }
@@ -443,70 +1299,137 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
443
1299
  : null
444
1300
  });
445
1301
 
446
- let res = await action.handleRequest(
447
- new SlateContext(
448
- ctx.config,
449
- { request: req, state: params.state },
450
- ctx.auth?.output!,
451
- slate.spec,
452
- logger
453
- )
1302
+ let context = new SlateContext(
1303
+ {},
1304
+ {
1305
+ request: req,
1306
+ webhookRegistrationPayload: params.webhookRegistrationPayload
1307
+ },
1308
+ {},
1309
+ slate.spec as any,
1310
+ logger
1311
+ );
1312
+ let res = await traceProviderCall(
1313
+ {
1314
+ component: 'action',
1315
+ functionName: 'webhookProcess',
1316
+ message: `Processing webhook request for trigger group ${formatEntityLabel(group.name, group.key)}`,
1317
+ successMessage: result =>
1318
+ `Extracted ${result.events.length} event(s) from webhook request for trigger group ${formatEntityLabel(group.name, group.key)}`,
1319
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while processing a webhook request`,
1320
+ metadata: {
1321
+ triggerGroupId: group.key,
1322
+ triggerGroupName: group.name,
1323
+ requestMethod: params.method,
1324
+ hasRequestBody: !!params.body
1325
+ },
1326
+ onSuccess: result => ({
1327
+ eventCount: result.events.length,
1328
+ hasResponse: result.response !== undefined
1329
+ })
1330
+ },
1331
+ () => runWithContext(context, () => group.webhook!.process(context))
454
1332
  );
455
1333
 
456
- return { inputs: res.inputs, updatedState: res.updatedState };
1334
+ for (let event of res.events) {
1335
+ if (!event.matchers) {
1336
+ throw new ServiceError(
1337
+ preconditionFailedError({
1338
+ message: `Trigger group "${group.key}" process handler must return matchers for every event (return [] if there is nothing to assert)`
1339
+ })
1340
+ );
1341
+ }
1342
+ }
1343
+
1344
+ let response =
1345
+ res.response === undefined
1346
+ ? undefined
1347
+ : await serializeWebhookHttpResponse(res.response);
1348
+
1349
+ return withRequestTraces(context, {
1350
+ events: res.events.map(event => ({
1351
+ matchers: event.matchers,
1352
+ payload: event.payload,
1353
+ idempotencyKey: event.idempotencyKey,
1354
+ triggerIds: evaluateTriggerMatches(slate, group.key, event.payload)
1355
+ })),
1356
+ response
1357
+ });
457
1358
  });
458
1359
 
459
- manager.onRequest('slates/action.trigger.webhook_register', async ({ params }) => {
1360
+ manager.onRequest('slates/trigger_group.routing_matchers.get', async ({ params }) => {
460
1361
  let ctx = getContextFull();
461
- let action = getActionWithType(slate, 'trigger', params.actionId);
462
-
463
- if (!action.autoRegisterWebhook) {
464
- throw new ServiceError(
465
- badRequestError({
466
- message: `Trigger action does not support webhook auto-registration: ${params.actionId}`
1362
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1363
+
1364
+ let context = new SlateContext(ctx.config, {}, ctx.auth?.output!, slate.spec, logger);
1365
+ let matchers = await traceProviderCall(
1366
+ {
1367
+ component: 'action',
1368
+ functionName: 'routingMatchers',
1369
+ message: `Getting routing matchers for trigger group ${formatEntityLabel(group.name, group.key)}`,
1370
+ successMessage: result =>
1371
+ `Retrieved ${result.length} routing matcher(s) for trigger group ${formatEntityLabel(group.name, group.key)}`,
1372
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while getting routing matchers`,
1373
+ metadata: {
1374
+ triggerGroupId: group.key,
1375
+ triggerGroupName: group.name
1376
+ },
1377
+ onSuccess: result => ({
1378
+ matcherCount: result.length
467
1379
  })
468
- );
469
- }
470
-
471
- let res = await action.autoRegisterWebhook(
472
- new SlateContext(
473
- ctx.config,
474
- { webhookBaseUrl: params.webhookBaseUrl },
475
- ctx.auth?.output!,
476
- slate.spec,
477
- logger
478
- )
1380
+ },
1381
+ () => runWithContext(context, () => group.routingMatchers(context))
479
1382
  );
480
1383
 
481
- return { registrationDetails: res.registrationDetails, state: res.state };
1384
+ return withRequestTraces(context, { matchers });
482
1385
  });
483
1386
 
484
- manager.onRequest('slates/action.trigger.webhook_unregister', async ({ params }) => {
1387
+ manager.onRequest('slates/trigger_group.polling.poll', async ({ params }) => {
485
1388
  let ctx = getContextFull();
486
- let action = getActionWithType(slate, 'trigger', params.actionId);
487
-
488
- if (!action.autoUnregisterWebhook) {
1389
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1390
+ if (group.source !== 'polling' || !group.polling) {
489
1391
  throw new ServiceError(
490
1392
  badRequestError({
491
- message: `Trigger action does not support webhook auto-unregistration: ${params.actionId}`
1393
+ message: `Trigger group does not support polling: ${params.triggerGroupId}`
492
1394
  })
493
1395
  );
494
1396
  }
495
1397
 
496
- await action.autoUnregisterWebhook(
497
- new SlateContext(
498
- ctx.config,
499
- {
500
- webhookBaseUrl: params.webhookBaseUrl,
501
- registrationDetails: params.registrationDetails,
502
- state: params.state
1398
+ let context = new SlateContext(
1399
+ ctx.config,
1400
+ { state: params.state },
1401
+ ctx.auth?.output!,
1402
+ slate.spec,
1403
+ logger
1404
+ );
1405
+ let res = await traceProviderCall(
1406
+ {
1407
+ component: 'action',
1408
+ functionName: 'pollEvents',
1409
+ message: `Polling events for trigger group ${formatEntityLabel(group.name, group.key)}`,
1410
+ successMessage: result =>
1411
+ `Polled ${result.events.length} event(s) for trigger group ${formatEntityLabel(group.name, group.key)}`,
1412
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while polling events`,
1413
+ metadata: {
1414
+ triggerGroupId: group.key,
1415
+ triggerGroupName: group.name,
1416
+ hasPreviousState: params.state !== null
503
1417
  },
504
- ctx.auth?.output!,
505
- slate.spec,
506
- logger
507
- )
1418
+ onSuccess: result => ({
1419
+ eventCount: result.events.length,
1420
+ hasUpdatedState: result.updatedState !== undefined
1421
+ })
1422
+ },
1423
+ () => runWithContext(context, () => group.polling!.pollEvents(context))
508
1424
  );
509
1425
 
510
- return {};
1426
+ return withRequestTraces(context, {
1427
+ updatedState: res.updatedState,
1428
+ events: res.events.map(event => ({
1429
+ payload: event.payload,
1430
+ idempotencyKey: event.idempotencyKey,
1431
+ triggerIds: evaluateTriggerMatches(slate, group.key, event.payload)
1432
+ }))
1433
+ });
511
1434
  });
512
1435
  });