@slates/provider-handler 1.0.0-rc.42 → 1.0.0-rc.44

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.ts CHANGED
@@ -9,7 +9,12 @@ import {
9
9
  SLATES_PROTOCOL_VERSION,
10
10
  type SlatesMessageAuthAdminCallbackHandleRequest,
11
11
  type SlatesMessageAuthAuthorizationUrlGetRequest,
12
- type SlatesParticipant
12
+ type SlatesMessageTriggerGroupWebhookProcessRequest,
13
+ type SlatesParticipant,
14
+ type SlatesRuntimeInfo,
15
+ slatesTriggerGroupWebhookDeliveryAuthentication,
16
+ slatesTriggerGroupWebhookExpansionResult,
17
+ slatesTriggerGroupWebhookRegistrationResult
13
18
  } from '@slates/proto';
14
19
  import {
15
20
  AuthConfigSecretRedactor,
@@ -22,6 +27,8 @@ import {
22
27
  SlateLogger,
23
28
  type SlateLogListener,
24
29
  SlatePublicContext,
30
+ type SlateRuntimeInfo,
31
+ type SlateTriggerGroup,
25
32
  uploadAttachmentDirect
26
33
  } from '@slates/provider';
27
34
  import { PQueue } from './pQueue';
@@ -36,6 +43,7 @@ import {
36
43
  getExposedTriggerGroups,
37
44
  getTriggerGroup,
38
45
  getWebhookAutoRegistration,
46
+ getWebhookDeliveryConfig,
39
47
  getWebhookManualRegistration,
40
48
  isMappableTrigger,
41
49
  mapAction,
@@ -50,6 +58,39 @@ import { serializeWebhookHttpResponse } from './webhook';
50
58
 
51
59
  let DEFAULT_MAX_ATTACHMENT_SIZE_BYTES = 100_000_000;
52
60
 
61
+ let validateTriggerGroupSelection = (
62
+ group: SlateTriggerGroup<any, any>,
63
+ selection: Record<string, any> | undefined
64
+ ) =>
65
+ group.selectionSchema
66
+ ? validate(
67
+ group.selectionSchema,
68
+ selection === undefined ? {} : selection,
69
+ 'trigger_group.selection',
70
+ `Invalid selection for trigger group "${group.key}"`
71
+ )
72
+ : (selection ?? {});
73
+
74
+ let validateWebhookRegistration = (result: unknown) =>
75
+ validate(
76
+ slatesTriggerGroupWebhookRegistrationResult,
77
+ result,
78
+ 'trigger_group.webhook.registration',
79
+ 'Webhook registration returned an invalid result'
80
+ );
81
+
82
+ let toWebhookRequest = (
83
+ params: Pick<
84
+ SlatesMessageTriggerGroupWebhookProcessRequest['params'],
85
+ 'url' | 'method' | 'headers' | 'body'
86
+ >
87
+ ) =>
88
+ new Request(params.url, {
89
+ method: params.method,
90
+ headers: params.headers,
91
+ body: params.body ? Uint8Array.from(atob(params.body.content), c => c.charCodeAt(0)) : null
92
+ });
93
+
53
94
  let routeAttachmentsThroughDirectUpload = async (
54
95
  attachments: SlateAttachment[] | undefined,
55
96
  live: SlateLiveInvocationInfo | null
@@ -189,6 +230,14 @@ let resolveTraceMessage = <ResultType>(
189
230
  result: ResultType
190
231
  ) => (typeof message === 'function' ? message(result) : message);
191
232
 
233
+ // Build fails if SlateRuntimeInfo drifts from the proto schema.
234
+ true satisfies [SlateRuntimeInfo, SlatesRuntimeInfo] extends [
235
+ SlatesRuntimeInfo,
236
+ SlateRuntimeInfo
237
+ ]
238
+ ? true
239
+ : false;
240
+
192
241
  export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
193
242
  slate: Slate<ConfigType, AuthType>,
194
243
  listeners: SlateLogListener[]
@@ -209,6 +258,7 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
209
258
  let liveInvocation = new State<Pick<SlateLiveInvocationInfo, 'token' | 'baseUrl'> | null>(
210
259
  null
211
260
  );
261
+ let runtimeInfo = new State<SlateRuntimeInfo | null>(null);
212
262
 
213
263
  let getLiveInvocation = (): SlateLiveInvocationInfo | null => {
214
264
  let directUpload = hubCapabilities.get()?.attachments?.directUpload;
@@ -335,9 +385,24 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
335
385
 
336
386
  let getAuthConfig = (): Record<string, any> => config.get()?.value ?? {};
337
387
 
338
- let getEmptyContext = () => new SlateContext({}, {}, {}, slate.spec as any, logger);
339
- let getAuthContext = () =>
340
- new SlateContext(getAuthConfig(), {}, {}, slate.spec as any, logger);
388
+ let createContext = <ConfigType extends {}, InputType extends {}, AuthType extends {}>(
389
+ config: ConfigType,
390
+ input: InputType,
391
+ auth: AuthType,
392
+ liveInvocationInfo: SlateLiveInvocationInfo | null = null
393
+ ) =>
394
+ new SlateContext(
395
+ config,
396
+ input,
397
+ auth,
398
+ slate.spec as any,
399
+ logger,
400
+ liveInvocationInfo,
401
+ runtimeInfo.get()
402
+ );
403
+
404
+ let getEmptyContext = () => createContext({}, {}, {});
405
+ let getAuthContext = () => createContext(getAuthConfig(), {}, {});
341
406
  let withRequestTraces = <Result extends Record<string, any>>(
342
407
  context: SlatePublicContext<any>,
343
408
  result: Result
@@ -481,11 +546,16 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
481
546
  });
482
547
  });
483
548
 
549
+ manager.onNotification('slates/hub.runtime_info.set', async ({ params }) => {
550
+ runtimeInfo.set(params.runtimeInfo);
551
+ });
552
+
484
553
  manager.onRequest('slates/provider.capabilities.get', async () => ({
485
554
  capabilities: {
486
555
  hub: {
487
556
  capabilitiesNotification: true,
488
- liveInvocation: true
557
+ liveInvocation: true,
558
+ runtimeInfo: true
489
559
  },
490
560
  provider: {
491
561
  triggerGroups:
@@ -1131,20 +1201,19 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1131
1201
 
1132
1202
  if (action.isPublic) {
1133
1203
  getContextBasic();
1134
- return invoke(new SlatePublicContext(input, slate.spec, logger, getLiveInvocation()));
1204
+ return invoke(
1205
+ new SlatePublicContext(
1206
+ input,
1207
+ slate.spec,
1208
+ logger,
1209
+ getLiveInvocation(),
1210
+ runtimeInfo.get()
1211
+ )
1212
+ );
1135
1213
  }
1136
1214
 
1137
1215
  let ctx = getContextFull();
1138
- return invoke(
1139
- new SlateContext(
1140
- ctx.config,
1141
- input,
1142
- ctx.auth?.output!,
1143
- slate.spec,
1144
- logger,
1145
- getLiveInvocation()
1146
- )
1147
- );
1216
+ return invoke(createContext(ctx.config, input, ctx.auth?.output!, getLiveInvocation()));
1148
1217
  });
1149
1218
 
1150
1219
  manager.onRequest('slates/action.trigger.map_event', async ({ params }) => {
@@ -1158,7 +1227,7 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1158
1227
  `Invalid event for trigger ID: ${params.actionId}`
1159
1228
  );
1160
1229
 
1161
- let context = new SlateContext(ctx.config, input, ctx.auth?.output!, slate.spec, logger);
1230
+ let context = createContext(ctx.config, input, ctx.auth?.output!);
1162
1231
  let res = await traceProviderCall(
1163
1232
  {
1164
1233
  component: 'action',
@@ -1246,12 +1315,13 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1246
1315
  let group = getTriggerGroup(slate, params.triggerGroupId);
1247
1316
  let autoRegistration = getWebhookAutoRegistration(group);
1248
1317
 
1249
- let context = new SlateContext(
1318
+ let context = createContext(
1250
1319
  ctx.config,
1251
- { pageToken: params.pageToken ?? null },
1252
- ctx.auth?.output!,
1253
- slate.spec,
1254
- logger
1320
+ {
1321
+ pageToken: params.pageToken ?? null,
1322
+ selection: validateTriggerGroupSelection(group, params.selection)
1323
+ },
1324
+ ctx.auth?.output!
1255
1325
  );
1256
1326
  let res = await traceProviderCall(
1257
1327
  {
@@ -1285,16 +1355,17 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1285
1355
  let group = getTriggerGroup(slate, params.triggerGroupId);
1286
1356
  let autoRegistration = getWebhookAutoRegistration(group);
1287
1357
 
1288
- let context = new SlateContext(
1358
+ let context = createContext(
1289
1359
  ctx.config,
1290
1360
  {
1291
1361
  webhookTargetIdentifier: params.webhookTargetIdentifier,
1292
1362
  webhookTargetPayload: params.webhookTargetPayload,
1293
- webhookUrl: params.webhookUrl
1363
+ webhookUrl: params.webhookUrl,
1364
+ operationId: params.operationId,
1365
+ selection: validateTriggerGroupSelection(group, params.selection),
1366
+ deliveryConfig: params.deliveryConfig
1294
1367
  },
1295
- ctx.auth?.output!,
1296
- slate.spec,
1297
- logger
1368
+ ctx.auth?.output!
1298
1369
  );
1299
1370
  let res = await traceProviderCall(
1300
1371
  {
@@ -1309,13 +1380,58 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1309
1380
  webhookTargetIdentifier: params.webhookTargetIdentifier
1310
1381
  }
1311
1382
  },
1312
- () => runWithContext(context, () => autoRegistration.webhookRegister(context))
1383
+ () =>
1384
+ runWithContext(context, async () =>
1385
+ validateWebhookRegistration(await autoRegistration.webhookRegister(context))
1386
+ )
1313
1387
  );
1314
1388
 
1315
- return withRequestTraces(context, {
1316
- webhookRegistrationIdentifier: res.webhookRegistrationIdentifier,
1317
- webhookRegistrationPayload: res.webhookRegistrationPayload
1318
- });
1389
+ return withRequestTraces(context, res);
1390
+ });
1391
+
1392
+ manager.onRequest('slates/trigger_group.webhook.renew', async ({ params }) => {
1393
+ let ctx = getContextFull();
1394
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1395
+ let autoRegistration = getWebhookAutoRegistration(group);
1396
+ let renew = autoRegistration.webhookRenew;
1397
+ if (!renew) {
1398
+ throw new ServiceError(
1399
+ badRequestError({
1400
+ message: `Trigger group does not support webhook renewal: ${params.triggerGroupId}`
1401
+ })
1402
+ );
1403
+ }
1404
+
1405
+ let context = createContext(
1406
+ ctx.config,
1407
+ {
1408
+ webhookRegistrationIdentifier: params.webhookRegistrationIdentifier,
1409
+ webhookRegistrationPayload: params.webhookRegistrationPayload,
1410
+ webhookUrl: params.webhookUrl,
1411
+ webhookTargetIdentifier: params.webhookTargetIdentifier,
1412
+ webhookTargetPayload: params.webhookTargetPayload,
1413
+ operationId: params.operationId,
1414
+ selection: validateTriggerGroupSelection(group, params.selection),
1415
+ deliveryConfig: params.deliveryConfig
1416
+ },
1417
+ ctx.auth?.output!
1418
+ );
1419
+ let res = await traceProviderCall(
1420
+ {
1421
+ component: 'action',
1422
+ functionName: 'webhookRenew',
1423
+ message: `Renewing webhook for trigger group ${formatEntityLabel(group.name, group.key)}`,
1424
+ successMessage: `Renewed webhook for trigger group ${formatEntityLabel(group.name, group.key)}`,
1425
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while renewing a webhook`,
1426
+ metadata: { triggerGroupId: group.key, triggerGroupName: group.name }
1427
+ },
1428
+ () =>
1429
+ runWithContext(context, async () =>
1430
+ validateWebhookRegistration(await renew(context))
1431
+ )
1432
+ );
1433
+
1434
+ return withRequestTraces(context, res);
1319
1435
  });
1320
1436
 
1321
1437
  manager.onRequest('slates/trigger_group.webhook.unregister', async ({ params }) => {
@@ -1323,15 +1439,13 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1323
1439
  let group = getTriggerGroup(slate, params.triggerGroupId);
1324
1440
  let autoRegistration = getWebhookAutoRegistration(group);
1325
1441
 
1326
- let context = new SlateContext(
1442
+ let context = createContext(
1327
1443
  ctx.config,
1328
1444
  {
1329
1445
  webhookRegistrationIdentifier: params.webhookRegistrationIdentifier,
1330
1446
  webhookRegistrationPayload: params.webhookRegistrationPayload
1331
1447
  },
1332
- ctx.auth?.output!,
1333
- slate.spec,
1334
- logger
1448
+ ctx.auth?.output!
1335
1449
  );
1336
1450
  await traceProviderCall(
1337
1451
  {
@@ -1357,13 +1471,7 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1357
1471
  let group = getTriggerGroup(slate, params.triggerGroupId);
1358
1472
  let manualRegistration = getWebhookManualRegistration(group);
1359
1473
 
1360
- let context = new SlateContext(
1361
- {},
1362
- { webhookUrl: params.webhookUrl },
1363
- {},
1364
- slate.spec as any,
1365
- logger
1366
- );
1474
+ let context = createContext({}, { webhookUrl: params.webhookUrl }, {});
1367
1475
  let res = await traceProviderCall(
1368
1476
  {
1369
1477
  component: 'action',
@@ -1398,16 +1506,14 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1398
1506
  );
1399
1507
  }
1400
1508
 
1401
- let context = new SlateContext(
1509
+ let context = createContext(
1402
1510
  {},
1403
1511
  {
1404
1512
  webhookUrl: params.webhookUrl,
1405
1513
  partialWebhookRegistrationPayload: params.partialWebhookRegistrationPayload,
1406
1514
  userWebhookRegistrationPayload: params.userWebhookRegistrationPayload
1407
1515
  },
1408
- {},
1409
- slate.spec as any,
1410
- logger
1516
+ {}
1411
1517
  );
1412
1518
  let res = await traceProviderCall(
1413
1519
  {
@@ -1439,23 +1545,13 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1439
1545
  );
1440
1546
  }
1441
1547
 
1442
- let req = new Request(params.url, {
1443
- method: params.method,
1444
- headers: params.headers,
1445
- body: params.body
1446
- ? Uint8Array.from(atob(params.body.content), c => c.charCodeAt(0))
1447
- : null
1448
- });
1449
-
1450
- let context = new SlateContext(
1548
+ let context = createContext(
1451
1549
  {},
1452
1550
  {
1453
- request: req,
1551
+ request: toWebhookRequest(params),
1454
1552
  webhookRegistrationPayload: params.webhookRegistrationPayload
1455
1553
  },
1456
- {},
1457
- slate.spec as any,
1458
- logger
1554
+ {}
1459
1555
  );
1460
1556
  let res = await traceProviderCall(
1461
1557
  {
@@ -1500,18 +1596,126 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1500
1596
  matchers: event.matchers,
1501
1597
  payload: event.payload,
1502
1598
  idempotencyKey: event.idempotencyKey,
1503
- triggerIds: evaluateTriggerMatches(slate, group.key, event.payload)
1599
+ triggerIds: group.webhook?.expandWebhook
1600
+ ? []
1601
+ : evaluateTriggerMatches(slate, group.key, event.payload)
1504
1602
  })),
1603
+ ...(group.webhook?.expandWebhook ? { requiresExpansion: true } : {}),
1505
1604
  response,
1506
1605
  skipped: res.skipped ?? null
1507
1606
  });
1508
1607
  });
1509
1608
 
1609
+ manager.onRequest(
1610
+ 'slates/trigger_group.webhook.delivery_authenticate',
1611
+ async ({ params }) => {
1612
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1613
+ let deliveryConfig = getWebhookDeliveryConfig(group);
1614
+
1615
+ let context = createContext(
1616
+ {},
1617
+ {
1618
+ request: toWebhookRequest(params),
1619
+ deliveryConfig: params.deliveryConfig
1620
+ },
1621
+ {}
1622
+ );
1623
+ let res = await traceProviderCall(
1624
+ {
1625
+ component: 'action',
1626
+ functionName: 'webhookDeliveryAuthenticate',
1627
+ message: `Authenticating a delivery for trigger group ${formatEntityLabel(group.name, group.key)}`,
1628
+ successMessage: result =>
1629
+ result.rejected
1630
+ ? `Rejected a delivery with status ${result.rejected.status} for trigger group ${formatEntityLabel(group.name, group.key)}`
1631
+ : `Authenticated a delivery for ${result.routingKeys.length} routing key(s) for trigger group ${formatEntityLabel(group.name, group.key)}`,
1632
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while authenticating a delivery`,
1633
+ metadata: {
1634
+ triggerGroupId: group.key,
1635
+ triggerGroupName: group.name,
1636
+ requestMethod: params.method,
1637
+ hasRequestBody: !!params.body
1638
+ },
1639
+ onSuccess: result => ({
1640
+ routingKeyCount: result.routingKeys.length,
1641
+ rejectedStatus: result.rejected?.status ?? null
1642
+ })
1643
+ },
1644
+ () =>
1645
+ runWithContext(context, async () => {
1646
+ let authentication = await deliveryConfig.authenticate(context);
1647
+ return validate(
1648
+ slatesTriggerGroupWebhookDeliveryAuthentication,
1649
+ authentication.rejected
1650
+ ? { routingKeys: [], rejected: authentication.rejected }
1651
+ : { routingKeys: authentication.routingKeys, rejected: null },
1652
+ 'trigger_group.webhook.delivery_authentication',
1653
+ 'Webhook delivery authentication returned an invalid result'
1654
+ );
1655
+ })
1656
+ );
1657
+
1658
+ return withRequestTraces(context, res);
1659
+ }
1660
+ );
1661
+
1662
+ manager.onRequest('slates/trigger_group.webhook.expand', async ({ params }) => {
1663
+ let ctx = getContextFull();
1664
+ let group = getTriggerGroup(slate, params.triggerGroupId);
1665
+ let expand = group.source === 'webhook' ? group.webhook?.expandWebhook : undefined;
1666
+ if (!expand) {
1667
+ throw new ServiceError(
1668
+ badRequestError({
1669
+ message: `Trigger group does not support webhook expansion: ${params.triggerGroupId}`
1670
+ })
1671
+ );
1672
+ }
1673
+
1674
+ let context = createContext(
1675
+ ctx.config,
1676
+ {
1677
+ payload: params.payload,
1678
+ state: params.state ?? null,
1679
+ selection: validateTriggerGroupSelection(group, params.selection),
1680
+ deliveryId: params.deliveryId,
1681
+ webhookRegistrationPayload: params.webhookRegistrationPayload
1682
+ },
1683
+ ctx.auth?.output!
1684
+ );
1685
+ let res = await traceProviderCall(
1686
+ {
1687
+ component: 'action',
1688
+ functionName: 'expandWebhook',
1689
+ message: `Expanding webhook delivery for trigger group ${formatEntityLabel(group.name, group.key)}`,
1690
+ successMessage: `Expanded webhook delivery for trigger group ${formatEntityLabel(group.name, group.key)}`,
1691
+ errorMessage: `Trigger group ${formatEntityLabel(group.name, group.key)} failed while expanding a webhook delivery`,
1692
+ metadata: { triggerGroupId: group.key, triggerGroupName: group.name }
1693
+ },
1694
+ () =>
1695
+ runWithContext(context, async () =>
1696
+ validate(
1697
+ slatesTriggerGroupWebhookExpansionResult,
1698
+ await expand(context),
1699
+ 'trigger_group.webhook.expansion',
1700
+ 'Webhook expansion returned an invalid result'
1701
+ )
1702
+ )
1703
+ );
1704
+
1705
+ return withRequestTraces(context, {
1706
+ ...(res.updatedState !== undefined ? { updatedState: res.updatedState } : {}),
1707
+ events: res.events.map(event => ({
1708
+ ...event,
1709
+ triggerIds: evaluateTriggerMatches(slate, group.key, event.payload)
1710
+ }))
1711
+ });
1712
+ });
1713
+
1510
1714
  manager.onRequest('slates/trigger_group.routing_matchers.get', async ({ params }) => {
1511
1715
  let ctx = getContextFull();
1512
1716
  let group = getTriggerGroup(slate, params.triggerGroupId);
1513
1717
 
1514
- let context = new SlateContext(ctx.config, {}, ctx.auth?.output!, slate.spec, logger);
1718
+ let context = createContext(ctx.config, {}, ctx.auth?.output!);
1515
1719
  let matchers = await traceProviderCall(
1516
1720
  {
1517
1721
  component: 'action',
@@ -1545,12 +1749,13 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1545
1749
  );
1546
1750
  }
1547
1751
 
1548
- let context = new SlateContext(
1752
+ let context = createContext(
1549
1753
  ctx.config,
1550
- { state: params.state },
1551
- ctx.auth?.output!,
1552
- slate.spec,
1553
- logger
1754
+ {
1755
+ state: params.state,
1756
+ selection: validateTriggerGroupSelection(group, params.selection)
1757
+ },
1758
+ ctx.auth?.output!
1554
1759
  );
1555
1760
  let res = await traceProviderCall(
1556
1761
  {
@@ -1599,13 +1804,7 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1599
1804
  let ctx = getContextFull();
1600
1805
  let { group, gateway } = getGatewayTriggerGroup(params.triggerGroupId);
1601
1806
 
1602
- let context = new SlateContext(
1603
- ctx.config,
1604
- { state: params.state },
1605
- ctx.auth?.output!,
1606
- slate.spec,
1607
- logger
1608
- );
1807
+ let context = createContext(ctx.config, { state: params.state }, ctx.auth?.output!);
1609
1808
  let res = await traceProviderCall(
1610
1809
  {
1611
1810
  component: 'action',
@@ -1630,12 +1829,10 @@ export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
1630
1829
  let ctx = getContextFull();
1631
1830
  let { group, gateway } = getGatewayTriggerGroup(params.triggerGroupId);
1632
1831
 
1633
- let context = new SlateContext(
1832
+ let context = createContext(
1634
1833
  ctx.config,
1635
1834
  { state: params.state, frames: params.frames, closed: params.closed ?? null },
1636
- ctx.auth?.output!,
1637
- slate.spec,
1638
- logger
1835
+ ctx.auth?.output!
1639
1836
  );
1640
1837
  let res = await traceProviderCall(
1641
1838
  {
@@ -0,0 +1,129 @@
1
+ import { SLATES_PROTOCOL_VERSION, SlatesProviderProtoHandlerManager } from '@slates/proto';
2
+ import {
3
+ getRuntimeInfo,
4
+ Slate,
5
+ SlateAuth,
6
+ SlateConfig,
7
+ SlatePublicTool,
8
+ SlateSpecification
9
+ } from '@slates/provider';
10
+ import { describe, expect, it } from 'vitest';
11
+ import { z } from 'zod';
12
+ import { createProviderHandler } from './index';
13
+
14
+ let runtimeInfo = {
15
+ metorial: {
16
+ apiUrl: 'https://api-us1.metorial.test',
17
+ region: 'us1',
18
+ environment: 'staging'
19
+ }
20
+ };
21
+
22
+ let createManager = async () => {
23
+ let spec = SlateSpecification.create({
24
+ key: 'runtime-info-test',
25
+ name: 'Runtime Info Test',
26
+ config: SlateConfig.create(z.object({ apiUrl: z.string().nullable() })).getDefaultConfig(
27
+ () => ({ apiUrl: getRuntimeInfo()?.metorial?.apiUrl ?? null })
28
+ ),
29
+ auth: SlateAuth.create().output(z.object({}))
30
+ });
31
+ let tool = SlatePublicTool.create(spec, {
32
+ key: 'read_runtime_info',
33
+ name: 'Read Runtime Info'
34
+ })
35
+ .input(z.object({}))
36
+ .output(z.object({ fromContext: z.any(), fromHelper: z.any() }))
37
+ .handleInvocation(async ctx => ({
38
+ output: { fromContext: ctx.runtimeInfo, fromHelper: getRuntimeInfo() },
39
+ message: 'ok'
40
+ }))
41
+ .build();
42
+ let slate = Slate.create({ spec, triggers: [], tools: [tool] });
43
+ let manager = await createProviderHandler(slate, []).run();
44
+
45
+ await SlatesProviderProtoHandlerManager.handleInput(manager, {
46
+ jsonrpc: '2.0',
47
+ method: 'slates/hello',
48
+ params: { protocol: SLATES_PROTOCOL_VERSION }
49
+ });
50
+ await SlatesProviderProtoHandlerManager.handleInput(manager, {
51
+ jsonrpc: '2.0',
52
+ method: 'slates/participant.set',
53
+ params: {
54
+ participants: [
55
+ { type: 'consumer', id: 'consumer', name: 'Consumer' },
56
+ { type: 'hub', id: 'hub', name: 'Hub' }
57
+ ]
58
+ }
59
+ });
60
+
61
+ return manager;
62
+ };
63
+
64
+ let setRuntimeInfo = (manager: SlatesProviderProtoHandlerManager) =>
65
+ SlatesProviderProtoHandlerManager.handleInput(manager, {
66
+ jsonrpc: '2.0',
67
+ method: 'slates/hub.runtime_info.set',
68
+ params: { runtimeInfo }
69
+ });
70
+
71
+ let invokeTool = (manager: SlatesProviderProtoHandlerManager) =>
72
+ SlatesProviderProtoHandlerManager.handleInput(manager, {
73
+ jsonrpc: '2.0',
74
+ id: 'tool',
75
+ method: 'slates/action.tool.invoke',
76
+ params: { actionId: 'read_runtime_info', input: {} }
77
+ });
78
+
79
+ let getDefaultConfig = (manager: SlatesProviderProtoHandlerManager) =>
80
+ SlatesProviderProtoHandlerManager.handleInput(manager, {
81
+ jsonrpc: '2.0',
82
+ id: 'config',
83
+ method: 'slates/config.get_default',
84
+ params: {}
85
+ });
86
+
87
+ describe('hub runtime info', () => {
88
+ it('advertises the runtime info capability', async () => {
89
+ let response = await SlatesProviderProtoHandlerManager.handleInput(await createManager(), {
90
+ jsonrpc: '2.0',
91
+ id: 'capabilities',
92
+ method: 'slates/provider.capabilities.get',
93
+ params: {}
94
+ });
95
+
96
+ expect(response).toMatchObject({
97
+ result: { capabilities: { hub: { runtimeInfo: true } } }
98
+ });
99
+ });
100
+
101
+ it('exposes runtime info to tools through ctx and the context helper', async () => {
102
+ let manager = await createManager();
103
+ await setRuntimeInfo(manager);
104
+
105
+ expect(await invokeTool(manager)).toMatchObject({
106
+ result: { output: { fromContext: runtimeInfo, fromHelper: runtimeInfo } }
107
+ });
108
+ });
109
+
110
+ it('exposes runtime info to handlers that receive no ctx argument', async () => {
111
+ let manager = await createManager();
112
+ await setRuntimeInfo(manager);
113
+
114
+ expect(await getDefaultConfig(manager)).toMatchObject({
115
+ result: { config: { apiUrl: runtimeInfo.metorial.apiUrl } }
116
+ });
117
+ });
118
+
119
+ it('returns null when the host sent no runtime info', async () => {
120
+ let manager = await createManager();
121
+
122
+ expect(await invokeTool(manager)).toMatchObject({
123
+ result: { output: { fromContext: null, fromHelper: null } }
124
+ });
125
+ expect(await getDefaultConfig(manager)).toMatchObject({
126
+ result: { config: { apiUrl: null } }
127
+ });
128
+ });
129
+ });