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

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