@slates/provider-handler 1.0.0-rc.4 → 1.0.0-rc.40

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