@slates/test 1.0.0-rc.17 → 1.0.0-rc.19

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.
package/src/index.test.ts CHANGED
@@ -6,7 +6,8 @@ import {
6
6
  SlateConfig,
7
7
  SlateSpecification,
8
8
  SlateTool,
9
- SlateTrigger
9
+ SlateTrigger,
10
+ SlateTriggerGroup
10
11
  } from '@slates/provider';
11
12
  import { mkdtemp, rm, writeFile } from 'fs/promises';
12
13
  import { tmpdir } from 'os';
@@ -18,12 +19,12 @@ import {
18
19
  expectSlateContract,
19
20
  expectSlateError,
20
21
  expectToolCall,
21
- handleSlateTriggerWebhook,
22
22
  loadSlatesRuntimeContext,
23
23
  mapSlateTriggerEvent,
24
- pollSlateTriggerEvents,
25
- registerSlateTriggerWebhook,
26
- unregisterSlateTriggerWebhook
24
+ pollSlateTriggerGroupEvents,
25
+ processSlateTriggerGroupWebhook,
26
+ registerSlateTriggerGroupWebhook,
27
+ unregisterSlateTriggerGroupWebhook
27
28
  } from './index';
28
29
 
29
30
  (globalThis as typeof globalThis & { expect?: typeof expect }).expect = expect;
@@ -171,69 +172,52 @@ let createDemoSlate = () => {
171
172
  }))
172
173
  .build();
173
174
 
174
- let webhookEcho = SlateTrigger.create(spec, {
175
- key: 'webhook_echo',
176
- name: 'Webhook Echo'
175
+ let webhookGroup = SlateTriggerGroup.create(spec, {
176
+ key: 'webhook_group',
177
+ name: 'Webhook Group'
177
178
  })
178
- .input(
179
- z.object({
180
- value: z.string()
181
- })
182
- )
183
- .output(
184
- z.object({
185
- echoed: z.string()
186
- })
187
- )
188
179
  .webhook({
189
- http: {
190
- methods: ['POST', 'OPTIONS'],
191
- sync: {
192
- mode: 'match',
193
- match: [
194
- {
195
- method: 'POST',
196
- hasHeader: 'x-demo-event'
197
- },
180
+ autoRegistration: {
181
+ webhookTargetList: async () => ({
182
+ targets: [
198
183
  {
199
- formBodyField: {
200
- path: 'mode',
201
- equals: 'subscribe'
202
- }
184
+ webhookTargetIdentifier: 'default',
185
+ name: 'Default',
186
+ metadata: {},
187
+ webhookTargetPayload: {},
188
+ targetOwnership: 'single_user'
203
189
  }
204
190
  ],
205
- timeoutMs: 4_000
206
- }
207
- },
208
- autoRegisterWebhook: async ctx => ({
209
- registrationDetails: {
210
- webhookBaseUrl: ctx.input.webhookBaseUrl,
211
- channelId: 'channel-1'
212
- },
213
- state: {
214
- registered: true
215
- }
216
- }),
217
- autoUnregisterWebhook: async ctx => {
218
- if (ctx.input.registrationDetails?.channelId !== 'channel-1') {
219
- throw new Error('Unexpected channel');
191
+ nextPageToken: null
192
+ }),
193
+ webhookRegister: async ctx => ({
194
+ webhookRegistrationIdentifier: 'reg-1',
195
+ webhookRegistrationPayload: {
196
+ webhookBaseUrl: ctx.input.webhookUrl,
197
+ channelId: 'channel-1'
198
+ }
199
+ }),
200
+ webhookUnregister: async ctx => {
201
+ if (ctx.input.webhookRegistrationPayload?.channelId !== 'channel-1') {
202
+ throw new Error('Unexpected channel');
203
+ }
220
204
  }
221
205
  },
222
- handleRequest: async ctx => {
223
- if (ctx.request.headers.get('x-demo-event') === 'ignore') {
224
- return { inputs: [] };
206
+ process: async ctx => {
207
+ if (ctx.input.request.headers.get('x-demo-event') === 'ignore') {
208
+ return { events: [] };
225
209
  }
226
210
 
227
211
  return {
228
- inputs: [
212
+ events: [
229
213
  {
230
- value: (await ctx.request.text()) || 'empty'
214
+ matchers: [],
215
+ payload: {
216
+ value: (await ctx.input.request.text()) || 'empty'
217
+ }
231
218
  }
232
219
  ],
233
- updatedState: {
234
- lastEvent: ctx.request.headers.get('x-demo-event') ?? 'unknown'
235
- },
236
- response: new Response(ctx.registrationDetails?.channelId ?? 'missing', {
220
+ response: new Response(ctx.input.webhookRegistrationPayload?.channelId ?? 'missing', {
237
221
  status: 201,
238
222
  headers: {
239
223
  'content-type': 'text/plain',
@@ -241,20 +225,14 @@ let createDemoSlate = () => {
241
225
  }
242
226
  })
243
227
  };
244
- },
245
- handleEvent: async ctx => ({
246
- type: 'demo.webhook',
247
- id: `webhook-${ctx.input.value}`,
248
- output: {
249
- echoed: ctx.input.value
250
- }
251
- })
228
+ }
252
229
  })
230
+ .routingMatchers(async () => [])
253
231
  .build();
254
232
 
255
- let pollEcho = SlateTrigger.create(spec, {
256
- key: 'poll_echo',
257
- name: 'Poll Echo'
233
+ let webhookEcho = SlateTrigger.create(spec, {
234
+ key: 'webhook_echo',
235
+ name: 'Webhook Echo'
258
236
  })
259
237
  .input(
260
238
  z.object({
@@ -266,27 +244,62 @@ let createDemoSlate = () => {
266
244
  echoed: z.string()
267
245
  })
268
246
  )
247
+ .triggerGroup(webhookGroup)
248
+ .matches(() => true)
249
+ .map(async ctx => ({
250
+ type: 'demo.webhook',
251
+ id: `webhook-${ctx.input.value}`,
252
+ output: {
253
+ echoed: ctx.input.value
254
+ }
255
+ }))
256
+ .build();
257
+
258
+ let pollGroup = SlateTriggerGroup.create(spec, {
259
+ key: 'poll_group',
260
+ name: 'Poll Group'
261
+ })
269
262
  .polling({
270
263
  pollEvents: async ctx => ({
271
- inputs: ctx.input.state?.seen ? [] : [{ value: 'poll-value' }],
264
+ events: ctx.input.state?.seen ? [] : [{ payload: { value: 'poll-value' } }],
272
265
  updatedState: {
273
266
  seen: true
274
267
  }
275
- }),
276
- handleEvent: async ctx => ({
277
- type: 'demo.poll',
278
- id: `poll-${ctx.input.value}`,
279
- output: {
280
- echoed: ctx.input.value
281
- }
282
268
  })
283
269
  })
270
+ .routingMatchers(async () => [])
271
+ .build();
272
+
273
+ let pollEcho = SlateTrigger.create(spec, {
274
+ key: 'poll_echo',
275
+ name: 'Poll Echo'
276
+ })
277
+ .input(
278
+ z.object({
279
+ value: z.string()
280
+ })
281
+ )
282
+ .output(
283
+ z.object({
284
+ echoed: z.string()
285
+ })
286
+ )
287
+ .triggerGroup(pollGroup)
288
+ .matches(() => true)
289
+ .map(async ctx => ({
290
+ type: 'demo.poll',
291
+ id: `poll-${ctx.input.value}`,
292
+ output: {
293
+ echoed: ctx.input.value
294
+ }
295
+ }))
284
296
  .build();
285
297
 
286
298
  return Slate.create({
287
299
  spec,
288
300
  tools: [echo, fail, attachmentEcho, downloadLink],
289
- triggers: [webhookEcho, pollEcho]
301
+ triggers: [webhookEcho, pollEcho],
302
+ triggerGroups: [webhookGroup, pollGroup]
290
303
  });
291
304
  };
292
305
 
@@ -416,6 +429,7 @@ describe('@slates/test', () => {
416
429
  },
417
430
  toolIds: ['echo', 'fail', 'attachment_echo', 'download_link'],
418
431
  triggerIds: ['webhook_echo', 'poll_echo'],
432
+ triggerGroupIds: ['webhook_group', 'poll_group'],
419
433
  authMethodIds: ['token_auth'],
420
434
  tools: [
421
435
  { id: 'echo', readOnly: false, destructive: false },
@@ -423,39 +437,14 @@ describe('@slates/test', () => {
423
437
  { id: 'attachment_echo', readOnly: true, destructive: false },
424
438
  { id: 'download_link', readOnly: true, destructive: false }
425
439
  ],
426
- triggers: [
427
- { id: 'webhook_echo', invocationType: 'webhook' },
428
- { id: 'poll_echo', invocationType: 'polling' }
440
+ triggers: [{ id: 'webhook_echo' }, { id: 'poll_echo' }],
441
+ triggerGroups: [
442
+ { id: 'webhook_group', invocationType: 'webhook' },
443
+ { id: 'poll_group', invocationType: 'polling' }
429
444
  ]
430
445
  });
431
446
 
432
447
  expect(contract.configSchema.properties.prefix.type).toBe('string');
433
- expect(contract.triggers.find(action => action.id === 'webhook_echo')?.invocation).toEqual(
434
- {
435
- type: 'webhook',
436
- autoRegistration: true,
437
- autoUnregistration: true,
438
- http: {
439
- methods: ['POST', 'OPTIONS'],
440
- sync: {
441
- mode: 'match',
442
- match: [
443
- {
444
- method: 'POST',
445
- hasHeader: 'x-demo-event'
446
- },
447
- {
448
- formBodyField: {
449
- path: 'mode',
450
- equals: 'subscribe'
451
- }
452
- }
453
- ],
454
- timeoutMs: 4_000
455
- }
456
- }
457
- }
458
- );
459
448
 
460
449
  await expectToolCall({
461
450
  client,
@@ -510,46 +499,44 @@ describe('@slates/test', () => {
510
499
  }
511
500
  });
512
501
 
513
- let registration = await registerSlateTriggerWebhook({
502
+ let registration = await registerSlateTriggerGroupWebhook({
514
503
  client,
515
- triggerId: 'webhook_echo',
516
- webhookBaseUrl: 'https://example.com/hooks/google-calendar'
504
+ triggerGroupId: 'webhook_group',
505
+ webhookTargetIdentifier: 'default',
506
+ webhookTargetPayload: {},
507
+ webhookUrl: 'https://example.com/hooks/google-calendar'
517
508
  });
518
509
  expect(registration).toMatchObject({
519
- registrationDetails: {
510
+ webhookRegistrationIdentifier: 'reg-1',
511
+ webhookRegistrationPayload: {
520
512
  webhookBaseUrl: 'https://example.com/hooks/google-calendar',
521
513
  channelId: 'channel-1'
522
- },
523
- state: {
524
- registered: true
525
514
  }
526
515
  });
527
516
 
528
- let ignored = await handleSlateTriggerWebhook({
517
+ let ignored = await processSlateTriggerGroupWebhook({
529
518
  client,
530
- triggerId: 'webhook_echo',
519
+ triggerGroupId: 'webhook_group',
531
520
  url: 'https://example.com/hooks/google-calendar',
532
521
  headers: {
533
522
  'x-demo-event': 'ignore'
534
- }
523
+ },
524
+ webhookRegistrationPayload: registration.webhookRegistrationPayload
535
525
  });
536
- expect(ignored.inputs).toEqual([]);
526
+ expect(ignored.events).toEqual([]);
537
527
 
538
- let handled = await handleSlateTriggerWebhook({
528
+ let handled = await processSlateTriggerGroupWebhook({
539
529
  client,
540
- triggerId: 'webhook_echo',
530
+ triggerGroupId: 'webhook_group',
541
531
  url: 'https://example.com/hooks/google-calendar',
542
532
  headers: {
543
533
  'x-demo-event': 'created'
544
534
  },
545
535
  body: 'payload-value',
546
- registrationDetails: registration.registrationDetails
536
+ webhookRegistrationPayload: registration.webhookRegistrationPayload
547
537
  });
548
538
  expect(handled).toMatchObject({
549
- inputs: [{ value: 'payload-value' }],
550
- updatedState: {
551
- lastEvent: 'created'
552
- },
539
+ events: [{ matchers: [], payload: { value: 'payload-value' } }],
553
540
  response: {
554
541
  status: 201,
555
542
  headers: {
@@ -576,11 +563,11 @@ describe('@slates/test', () => {
576
563
  });
577
564
  expect(mapped.id).toBe('webhook-payload-value');
578
565
 
579
- await unregisterSlateTriggerWebhook({
566
+ await unregisterSlateTriggerGroupWebhook({
580
567
  client,
581
- triggerId: 'webhook_echo',
582
- webhookBaseUrl: 'https://example.com/hooks/google-calendar',
583
- registrationDetails: registration.registrationDetails
568
+ triggerGroupId: 'webhook_group',
569
+ webhookRegistrationIdentifier: registration.webhookRegistrationIdentifier,
570
+ webhookRegistrationPayload: registration.webhookRegistrationPayload
584
571
  });
585
572
 
586
573
  await expectSlateError(
@@ -608,12 +595,12 @@ describe('@slates/test', () => {
608
595
  }
609
596
  });
610
597
 
611
- let initialPoll = await pollSlateTriggerEvents({
598
+ let initialPoll = await pollSlateTriggerGroupEvents({
612
599
  client,
613
- triggerId: 'poll_echo'
600
+ triggerGroupId: 'poll_group'
614
601
  });
615
602
  expect(initialPoll).toMatchObject({
616
- inputs: [{ value: 'poll-value' }],
603
+ events: [{ payload: { value: 'poll-value' } }],
617
604
  updatedState: {
618
605
  seen: true
619
606
  }
@@ -622,7 +609,7 @@ describe('@slates/test', () => {
622
609
  let mapped = await mapSlateTriggerEvent({
623
610
  client,
624
611
  triggerId: 'poll_echo',
625
- input: initialPoll.inputs[0]!,
612
+ input: initialPoll.events[0]!.payload,
626
613
  type: 'demo.poll',
627
614
  output: {
628
615
  echoed: 'poll-value'
@@ -630,12 +617,12 @@ describe('@slates/test', () => {
630
617
  });
631
618
  expect(mapped.id).toBe('poll-poll-value');
632
619
 
633
- let repeatedPoll = await pollSlateTriggerEvents({
620
+ let repeatedPoll = await pollSlateTriggerGroupEvents({
634
621
  client,
635
- triggerId: 'poll_echo',
622
+ triggerGroupId: 'poll_group',
636
623
  state: initialPoll.updatedState
637
624
  });
638
- expect(repeatedPoll.inputs).toEqual([]);
625
+ expect(repeatedPoll.events).toEqual([]);
639
626
  expect(repeatedPoll.updatedState).toEqual({
640
627
  seen: true
641
628
  });
package/src/runtime.ts CHANGED
@@ -65,6 +65,12 @@ export interface ExpectedSlateAction {
65
65
  description?: string;
66
66
  readOnly?: boolean;
67
67
  destructive?: boolean;
68
+ }
69
+
70
+ export interface ExpectedSlateTriggerGroup {
71
+ id: string;
72
+ name?: string;
73
+ description?: string;
68
74
  invocationType?: 'polling' | 'webhook';
69
75
  }
70
76
 
@@ -195,16 +201,20 @@ export let createLocalSlateTestClient = (opts: {
195
201
  }) =>
196
202
  createSlatesClient({
197
203
  transport: createLocalSlateTransport({ slate: opts.slate as LocalSlate }),
198
- state: opts.state,
204
+ state: {
205
+ capabilities: { triggers: true },
206
+ ...opts.state
207
+ },
199
208
  participants: opts.participants
200
209
  });
201
210
 
202
211
  export let getSlateContract = async (client: SlatesTestClient) => {
203
- let [provider, actions, authMethods, configSchema] = await Promise.all([
212
+ let [provider, actions, authMethods, configSchema, triggerGroups] = await Promise.all([
204
213
  client.identify(),
205
214
  client.listActions(),
206
215
  client.listAuthMethods(),
207
- client.getConfigSchema()
216
+ client.getConfigSchema(),
217
+ client.listTriggerGroups()
208
218
  ]);
209
219
 
210
220
  return {
@@ -216,6 +226,7 @@ export let getSlateContract = async (client: SlatesTestClient) => {
216
226
  triggers: actions.actions.filter(
217
227
  (action: SlatesAction): action is SlatesTriggerAction => action.type === 'action.trigger'
218
228
  ),
229
+ triggerGroups: triggerGroups.triggerGroups,
219
230
  authMethods: authMethods.authenticationMethods,
220
231
  configSchema: configSchema.schema
221
232
  };
@@ -244,6 +255,23 @@ let expectActionMatches = (
244
255
  if (expected.destructive !== undefined) {
245
256
  expect(actual?.tags?.destructive ?? false).toBe(expected.destructive);
246
257
  }
258
+ };
259
+
260
+ let expectTriggerGroupMatches = (
261
+ actual: Record<string, any> | undefined,
262
+ expected: ExpectedSlateTriggerGroup
263
+ ) => {
264
+ let expect = getVitestExpect();
265
+ expect(actual).toBeTruthy();
266
+ expect(actual?.id).toBe(expected.id);
267
+
268
+ if (expected.name !== undefined) {
269
+ expect(actual?.name).toBe(expected.name);
270
+ }
271
+
272
+ if (expected.description !== undefined) {
273
+ expect(actual?.description).toBe(expected.description);
274
+ }
247
275
 
248
276
  if (expected.invocationType !== undefined) {
249
277
  expect((actual as { invocation?: { type?: string } } | undefined)?.invocation?.type).toBe(
@@ -261,9 +289,11 @@ export let expectSlateContract = async (d: {
261
289
  };
262
290
  toolIds?: string[];
263
291
  triggerIds?: string[];
292
+ triggerGroupIds?: string[];
264
293
  authMethodIds?: string[];
265
294
  tools?: ExpectedSlateAction[];
266
295
  triggers?: ExpectedSlateAction[];
296
+ triggerGroups?: ExpectedSlateTriggerGroup[];
267
297
  }) => {
268
298
  let expect = getVitestExpect();
269
299
  let contract = await getSlateContract(d.client);
@@ -288,6 +318,10 @@ export let expectSlateContract = async (d: {
288
318
  );
289
319
  }
290
320
 
321
+ if (d.triggerGroupIds) {
322
+ expect(contract.triggerGroups.map(group => group.id)).toEqual(d.triggerGroupIds);
323
+ }
324
+
291
325
  if (d.authMethodIds) {
292
326
  expect(contract.authMethods.map((method: SlateAuthenticationMethod) => method.id)).toEqual(
293
327
  d.authMethodIds
@@ -308,59 +342,70 @@ export let expectSlateContract = async (d: {
308
342
  );
309
343
  }
310
344
 
345
+ for (let triggerGroup of d.triggerGroups ?? []) {
346
+ expectTriggerGroupMatches(
347
+ contract.triggerGroups.find(group => group.id === triggerGroup.id),
348
+ triggerGroup
349
+ );
350
+ }
351
+
311
352
  return contract;
312
353
  };
313
354
 
314
- export let registerSlateTriggerWebhook = async (d: {
355
+ export let registerSlateTriggerGroupWebhook = async (d: {
315
356
  client: SlatesTestClient;
316
- triggerId: string;
317
- webhookBaseUrl: string;
318
- }) => d.client.registerTriggerWebhook(d.triggerId, d.webhookBaseUrl);
357
+ triggerGroupId: string;
358
+ webhookTargetIdentifier: string;
359
+ webhookTargetPayload: any;
360
+ webhookUrl: string;
361
+ }) =>
362
+ d.client.registerTriggerGroupWebhook({
363
+ triggerGroupId: d.triggerGroupId,
364
+ webhookTargetIdentifier: d.webhookTargetIdentifier,
365
+ webhookTargetPayload: d.webhookTargetPayload,
366
+ webhookUrl: d.webhookUrl
367
+ });
319
368
 
320
- export let pollSlateTriggerEvents = async (d: {
369
+ export let pollSlateTriggerGroupEvents = async (d: {
321
370
  client: SlatesTestClient;
322
- triggerId: string;
371
+ triggerGroupId: string;
323
372
  state?: any;
324
373
  }) => {
325
374
  d.client.ensureSession();
326
- return d.client.request('slates/action.trigger.poll_events', {
327
- actionId: d.triggerId,
375
+ return d.client.pollTriggerGroup({
376
+ triggerGroupId: d.triggerGroupId,
328
377
  state: d.state ?? null
329
378
  });
330
379
  };
331
380
 
332
- export let handleSlateTriggerWebhook = async (d: {
381
+ export let processSlateTriggerGroupWebhook = async (d: {
333
382
  client: SlatesTestClient;
334
- triggerId: string;
383
+ triggerGroupId: string;
335
384
  url: string;
336
385
  method?: string;
337
386
  headers?: Record<string, string>;
338
387
  body?: string | Uint8Array | null;
339
- state?: any;
340
- registrationDetails?: any;
388
+ webhookRegistrationPayload?: any;
341
389
  }) =>
342
- d.client.handleTriggerWebhook({
343
- actionId: d.triggerId,
390
+ d.client.processTriggerGroupWebhook({
391
+ triggerGroupId: d.triggerGroupId,
344
392
  url: d.url,
345
393
  method: d.method ?? 'POST',
346
394
  headers: d.headers,
347
395
  body: d.body,
348
- state: d.state,
349
- registrationDetails: d.registrationDetails
396
+ webhookRegistrationPayload: d.webhookRegistrationPayload ?? null
350
397
  });
351
398
 
352
- export let unregisterSlateTriggerWebhook = async (d: {
399
+ export let unregisterSlateTriggerGroupWebhook = async (d: {
353
400
  client: SlatesTestClient;
354
- triggerId: string;
355
- webhookBaseUrl: string;
356
- registrationDetails: any;
357
- state?: any;
401
+ triggerGroupId: string;
402
+ webhookRegistrationIdentifier: string;
403
+ webhookRegistrationPayload: any;
358
404
  }) =>
359
- d.client.unregisterTriggerWebhook({
360
- actionId: d.triggerId,
361
- webhookBaseUrl: d.webhookBaseUrl,
362
- registrationDetails: d.registrationDetails,
363
- state: d.state
405
+ d.client.unregisterTriggerGroupWebhook({
406
+ triggerGroupId: d.triggerGroupId,
407
+ webhookRegistrationIdentifier: d.webhookRegistrationIdentifier,
408
+ webhookRegistrationPayload: d.webhookRegistrationPayload
364
409
  });
365
410
 
366
411
  export let mapSlateTriggerEvent = async (d: {