@slates/provider-handler 1.0.0-rc.1
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/package.json +39 -0
- package/src/index.ts +512 -0
- package/src/spec.ts +129 -0
- package/src/state.ts +19 -0
- package/src/validation.ts +27 -0
- package/tsconfig.json +8 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slates/provider-handler",
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"author": "Tobias Herber",
|
|
8
|
+
"license": "Apache 2",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"source": "src/index.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"require": "./dist/index.cjs",
|
|
14
|
+
"import": "./dist/index.module.js",
|
|
15
|
+
"default": "./dist/index.module.js"
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.module.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"unpkg": "./dist/index.umd.js",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "vitest run --passWithNoTests",
|
|
23
|
+
"lint": "prettier src/**/*.ts --check",
|
|
24
|
+
"build": "microbundle"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@lowerdeck/error": "^1.0.8",
|
|
28
|
+
"@slates/proto": "^1.0.0-rc.1",
|
|
29
|
+
"@slates/provider": "^1.0.0-rc.1",
|
|
30
|
+
"@slates/provider-handler": "^1.0.0-rc.1",
|
|
31
|
+
"zod": "^4.2.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"microbundle": "^0.15.1",
|
|
35
|
+
"@slates/tsconfig": "^1.0.0",
|
|
36
|
+
"typescript": "5.8.2",
|
|
37
|
+
"vitest": "^3.1.2"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
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';
|
|
5
|
+
import { State } from './state';
|
|
6
|
+
import { validate } from './validation';
|
|
7
|
+
|
|
8
|
+
export let createProviderHandler = <ConfigType extends {}, AuthType extends {}>(
|
|
9
|
+
slate: Slate<ConfigType, AuthType>,
|
|
10
|
+
listeners: SlateLogListener[]
|
|
11
|
+
) =>
|
|
12
|
+
createSlatesProviderProtoHandler(async manager => {
|
|
13
|
+
let protocol = new State<string | null>(null);
|
|
14
|
+
let participants = new State<SlatesParticipant[] | null>(null);
|
|
15
|
+
|
|
16
|
+
let auth = new State<{ authenticationMethodId: string; output: AuthType } | null>(null);
|
|
17
|
+
let config = new State<{ value: ConfigType } | null>(null);
|
|
18
|
+
|
|
19
|
+
let session = new State<{ id: string; state: any } | null>(null);
|
|
20
|
+
|
|
21
|
+
let logger = new SlateLogger(listeners);
|
|
22
|
+
|
|
23
|
+
let getContextBasic = () => {
|
|
24
|
+
let currentProtocol = protocol.get();
|
|
25
|
+
let currentParticipants = participants.get();
|
|
26
|
+
|
|
27
|
+
if (!currentProtocol || !currentParticipants) {
|
|
28
|
+
throw new ServiceError(
|
|
29
|
+
preconditionFailedError({
|
|
30
|
+
message: 'Connection context has not been initialized'
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
protocol: currentProtocol,
|
|
37
|
+
participants: currentParticipants
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let getContextFull = () => {
|
|
42
|
+
let basic = getContextBasic();
|
|
43
|
+
|
|
44
|
+
let currentConfig = config.get();
|
|
45
|
+
let currentSession = session.get();
|
|
46
|
+
let currentAuth = auth.get();
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
!currentConfig ||
|
|
50
|
+
!currentSession ||
|
|
51
|
+
(!currentAuth && slate.spec.auth.authStack.length > 0)
|
|
52
|
+
) {
|
|
53
|
+
throw new ServiceError(
|
|
54
|
+
preconditionFailedError({
|
|
55
|
+
message: 'Session context has not been initialized'
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
...basic,
|
|
62
|
+
config: currentConfig.value,
|
|
63
|
+
session: currentSession,
|
|
64
|
+
auth: currentAuth
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
manager.onNotification('slates/hello', async ({ params }) => {
|
|
69
|
+
protocol.set(params.protocol);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
manager.onNotification('slates/participant.set', async ({ params }) => {
|
|
73
|
+
if (!protocol.get()) {
|
|
74
|
+
throw new ServiceError(
|
|
75
|
+
preconditionFailedError({ message: 'Connection protocol has not been initialized' })
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
participants.set(params.participants);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
manager.onNotification('slates/auth.set', async ({ params }) => {
|
|
83
|
+
getContextBasic();
|
|
84
|
+
getAuthMethod(slate, params.authenticationMethodId); // validate method ID
|
|
85
|
+
|
|
86
|
+
let valRes = validate(
|
|
87
|
+
slate.spec.authSchema,
|
|
88
|
+
params.output,
|
|
89
|
+
'auth',
|
|
90
|
+
`Invalid authentication output for method ID: ${params.authenticationMethodId}`
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
auth.set({
|
|
94
|
+
authenticationMethodId: params.authenticationMethodId,
|
|
95
|
+
output: valRes
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
manager.onNotification('slates/config.set', async ({ params }) => {
|
|
100
|
+
getContextBasic();
|
|
101
|
+
|
|
102
|
+
let value = validate(
|
|
103
|
+
slate.spec.configSchema,
|
|
104
|
+
params.config,
|
|
105
|
+
'config',
|
|
106
|
+
'Invalid configuration'
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
config.set({ value });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
manager.onNotification('slates/session.start', async ({ params }) => {
|
|
113
|
+
getContextBasic();
|
|
114
|
+
|
|
115
|
+
session.set({
|
|
116
|
+
id: params.sessionId,
|
|
117
|
+
state: params.state
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
manager.onRequest('slates/config.changed', async ({ params }) => {
|
|
122
|
+
getContextBasic();
|
|
123
|
+
|
|
124
|
+
let newConfig = validate(
|
|
125
|
+
slate.spec.config.configSchema,
|
|
126
|
+
params.newConfig,
|
|
127
|
+
'config',
|
|
128
|
+
'Invalid configuration'
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
let configChanged = slate.spec.config.handlers.configChanged;
|
|
132
|
+
if (!configChanged) {
|
|
133
|
+
return { success: true, config: newConfig };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let updatedConfig = await configChanged({
|
|
137
|
+
previousConfig: params.previousConfig as ConfigType | null,
|
|
138
|
+
newConfig
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return { success: true, config: updatedConfig?.config ?? newConfig };
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
manager.onRequest('slates/config.get_default', async ({ params }) => {
|
|
145
|
+
getContextBasic();
|
|
146
|
+
|
|
147
|
+
let getDefaultConfig = slate.spec.config.handlers.getDefaultConfig;
|
|
148
|
+
if (!getDefaultConfig) {
|
|
149
|
+
return { config: null };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let defaultConfig = await getDefaultConfig();
|
|
153
|
+
return { config: defaultConfig };
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
manager.onRequest('slates/config.schema.get', async ({ params }) => {
|
|
157
|
+
getContextBasic();
|
|
158
|
+
|
|
159
|
+
return { schema: slate.spec.configSchema.toJSONSchema() };
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
manager.onRequest('slates/provider.identify', async ({ params }) => {
|
|
163
|
+
getContextBasic();
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
protocol: 'slates@2026-01-01',
|
|
167
|
+
provider: {
|
|
168
|
+
type: 'provider',
|
|
169
|
+
id: slate.spec.key,
|
|
170
|
+
name: slate.spec.name,
|
|
171
|
+
description: slate.spec.description,
|
|
172
|
+
metadata: slate.spec.parameters.metadata
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
manager.onRequest('slates/auth.methods.list', async ({ params }) => {
|
|
178
|
+
getContextBasic();
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
authenticationMethods: slate.spec.auth.authStack.map(m => mapAuthMethod(slate, m))
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
manager.onRequest('slates/auth.method.get', async ({ params }) => {
|
|
186
|
+
getContextBasic();
|
|
187
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
authenticationMethod: mapAuthMethod(slate, authMethod)
|
|
191
|
+
};
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
manager.onRequest('slates/auth.input.get_default', async ({ params }) => {
|
|
195
|
+
getContextBasic();
|
|
196
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
197
|
+
|
|
198
|
+
if (!authMethod.getDefaultInput) {
|
|
199
|
+
return { input: null };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { input: await authMethod.getDefaultInput() };
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
manager.onRequest('slates/auth.input.changed', async ({ params }) => {
|
|
206
|
+
getContextBasic();
|
|
207
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
208
|
+
|
|
209
|
+
if (!authMethod.onInputChanged) {
|
|
210
|
+
return { success: true, input: params.newInput };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let updatedInput = await authMethod.onInputChanged({
|
|
214
|
+
previousInput: params.previousInput as any | null,
|
|
215
|
+
newInput: params.newInput
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return { success: true, input: updatedInput?.input ?? params.newInput };
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
manager.onRequest('slates/auth.output.get', async ({ params }) => {
|
|
222
|
+
getContextBasic();
|
|
223
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
224
|
+
|
|
225
|
+
let input = params.input;
|
|
226
|
+
|
|
227
|
+
if (authMethod.inputSchema) {
|
|
228
|
+
input = validate(
|
|
229
|
+
authMethod.inputSchema,
|
|
230
|
+
input,
|
|
231
|
+
'auth',
|
|
232
|
+
`Invalid authentication input for method ID: ${params.authenticationMethodId}`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if ('getOutput' in authMethod) {
|
|
237
|
+
let outputRes = await authMethod.getOutput({ input });
|
|
238
|
+
return { output: outputRes.output };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { output: input as any };
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
manager.onRequest('slates/auth.authorization_callback.handle', async ({ params }) => {
|
|
245
|
+
getContextBasic();
|
|
246
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
247
|
+
|
|
248
|
+
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
|
+
});
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
output: callbackRes.output,
|
|
261
|
+
input: callbackRes.input
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
throw new ServiceError(
|
|
266
|
+
preconditionFailedError({
|
|
267
|
+
message: `Authentication method does not support authorization callback handling: ${params.authenticationMethodId}`
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
manager.onRequest('slates/auth.authorization_url.get', async ({ params }) => {
|
|
273
|
+
getContextBasic();
|
|
274
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
275
|
+
|
|
276
|
+
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
|
+
});
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
authorizationUrl: urlRes.url,
|
|
288
|
+
input: urlRes.input
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
throw new ServiceError(
|
|
293
|
+
preconditionFailedError({
|
|
294
|
+
message: `Authentication method does not support authorization URL retrieval: ${params.authenticationMethodId}`
|
|
295
|
+
})
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
manager.onRequest('slates/auth.profile.get', async ({ params }) => {
|
|
300
|
+
getContextBasic();
|
|
301
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
302
|
+
|
|
303
|
+
if (authMethod.getProfile) {
|
|
304
|
+
let profileRes = await authMethod.getProfile({
|
|
305
|
+
output: params.output as any,
|
|
306
|
+
input: params.input,
|
|
307
|
+
scopes: params.scopes
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
profile: profileRes.profile
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
throw new ServiceError(
|
|
316
|
+
preconditionFailedError({
|
|
317
|
+
message: `Authentication method does not support profile retrieval: ${params.authenticationMethodId}`
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
manager.onRequest('slates/auth.token_refresh.handle', async ({ params }) => {
|
|
323
|
+
getContextBasic();
|
|
324
|
+
let authMethod = getAuthMethod(slate, params.authenticationMethodId);
|
|
325
|
+
|
|
326
|
+
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
|
+
});
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
output: refreshRes.output,
|
|
337
|
+
input: refreshRes.input
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
throw new ServiceError(
|
|
342
|
+
preconditionFailedError({
|
|
343
|
+
message: `Authentication method does not support token refresh handling: ${params.authenticationMethodId}`
|
|
344
|
+
})
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
manager.onRequest('slates/actions.list', async ({ params }) => {
|
|
349
|
+
getContextBasic();
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
actions: slate.actions.map(a => mapAction(slate, a))
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
manager.onRequest('slates/action.get', async ({ params }) => {
|
|
357
|
+
getContextBasic();
|
|
358
|
+
let action = getAction(slate, params.actionId);
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
action: mapAction(slate, action)
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
manager.onRequest('slates/action.tool.invoke', async ({ params }) => {
|
|
366
|
+
let ctx = getContextFull();
|
|
367
|
+
let action = getActionWithType(slate, 'tool', params.actionId);
|
|
368
|
+
|
|
369
|
+
let input = validate(
|
|
370
|
+
action.inputSchema,
|
|
371
|
+
params.input,
|
|
372
|
+
'input',
|
|
373
|
+
`Invalid input for tool ID: ${params.actionId}`
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
let res = await action.handleInvocation(
|
|
377
|
+
new SlateContext(ctx.config, input, ctx.auth?.output!, slate.spec, logger)
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
return { output: res.output, message: res.message };
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
manager.onRequest('slates/action.trigger.map_event', async ({ params }) => {
|
|
384
|
+
let ctx = getContextFull();
|
|
385
|
+
let action = getActionWithType(slate, 'trigger', params.actionId);
|
|
386
|
+
|
|
387
|
+
let input = validate(
|
|
388
|
+
action.inputSchema,
|
|
389
|
+
params.input,
|
|
390
|
+
'input',
|
|
391
|
+
`Invalid event for trigger ID: ${params.actionId}`
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
let res = await action.handleEvent(
|
|
395
|
+
new SlateContext(ctx.config, input, ctx.auth?.output!, slate.spec, logger)
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
return { id: res.id, type: res.type, output: res.output };
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
manager.onRequest('slates/action.trigger.poll_events', async ({ params }) => {
|
|
402
|
+
let ctx = getContextFull();
|
|
403
|
+
let action = getActionWithType(slate, 'trigger', params.actionId);
|
|
404
|
+
|
|
405
|
+
if (!action.pollEvents) {
|
|
406
|
+
throw new ServiceError(
|
|
407
|
+
badRequestError({
|
|
408
|
+
message: `Trigger action does not support polling: ${params.actionId}`
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
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
|
+
)
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
return { inputs: res.inputs, updatedState: res.updatedState };
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
manager.onRequest('slates/action.trigger.webhook_handle', async ({ params }) => {
|
|
427
|
+
let ctx = getContextFull();
|
|
428
|
+
let action = getActionWithType(slate, 'trigger', params.actionId);
|
|
429
|
+
|
|
430
|
+
if (!action.handleRequest) {
|
|
431
|
+
throw new ServiceError(
|
|
432
|
+
badRequestError({
|
|
433
|
+
message: `Trigger action does not support webhook requests: ${params.actionId}`
|
|
434
|
+
})
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
let req = new Request(params.url, {
|
|
439
|
+
method: params.method,
|
|
440
|
+
headers: params.headers,
|
|
441
|
+
body: params.body
|
|
442
|
+
? Uint8Array.from(atob(params.body.content), c => c.charCodeAt(0))
|
|
443
|
+
: null
|
|
444
|
+
});
|
|
445
|
+
|
|
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
|
+
)
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
return { inputs: res.inputs, updatedState: res.updatedState };
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
manager.onRequest('slates/action.trigger.webhook_register', async ({ params }) => {
|
|
460
|
+
let ctx = getContextFull();
|
|
461
|
+
let action = getActionWithType(slate, 'trigger', params.actionId);
|
|
462
|
+
|
|
463
|
+
if (!action.autoRegisterWebhook) {
|
|
464
|
+
throw new ServiceError(
|
|
465
|
+
badRequestError({
|
|
466
|
+
message: `Trigger action does not support webhook auto-registration: ${params.actionId}`
|
|
467
|
+
})
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
let res = await action.autoRegisterWebhook(
|
|
472
|
+
new SlateContext(
|
|
473
|
+
ctx.config,
|
|
474
|
+
{ webhookBaseUrl: params.webhookBaseUrl },
|
|
475
|
+
ctx.auth?.output!,
|
|
476
|
+
slate.spec,
|
|
477
|
+
logger
|
|
478
|
+
)
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
return { registrationDetails: res.registrationDetails, state: res.state };
|
|
482
|
+
});
|
|
483
|
+
|
|
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) {
|
|
489
|
+
throw new ServiceError(
|
|
490
|
+
badRequestError({
|
|
491
|
+
message: `Trigger action does not support webhook auto-unregistration: ${params.actionId}`
|
|
492
|
+
})
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
await action.autoUnregisterWebhook(
|
|
497
|
+
new SlateContext(
|
|
498
|
+
ctx.config,
|
|
499
|
+
{
|
|
500
|
+
webhookBaseUrl: params.webhookBaseUrl,
|
|
501
|
+
registrationDetails: params.registrationDetails,
|
|
502
|
+
state: params.state
|
|
503
|
+
},
|
|
504
|
+
ctx.auth?.output!,
|
|
505
|
+
slate.spec,
|
|
506
|
+
logger
|
|
507
|
+
)
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
return {};
|
|
511
|
+
});
|
|
512
|
+
});
|
package/src/spec.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { badRequestError, notFoundError, ServiceError } from '@lowerdeck/error';
|
|
2
|
+
import { SlateAuthenticationMethod, SlatesAction } from '@slates/proto';
|
|
3
|
+
import { Slate, SlateDefaultPollingIntervalSeconds } from '@slates/provider';
|
|
4
|
+
import z from 'zod';
|
|
5
|
+
|
|
6
|
+
export let getAuthMethod = <ConfigType extends {}, AuthType extends {}>(
|
|
7
|
+
slate: Slate<ConfigType, AuthType>,
|
|
8
|
+
authenticationMethodId: string
|
|
9
|
+
) => {
|
|
10
|
+
let authMethod = slate.spec.auth.authStack.find(m => m.key == authenticationMethodId);
|
|
11
|
+
if (!authMethod) {
|
|
12
|
+
throw new ServiceError(
|
|
13
|
+
badRequestError({
|
|
14
|
+
message: `Invalid authentication method ID: ${authenticationMethodId}`
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return authMethod;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export let mapAuthMethod = <ConfigType extends {}, AuthType extends {}>(
|
|
23
|
+
slate: Slate<ConfigType, AuthType>,
|
|
24
|
+
m: ReturnType<typeof getAuthMethod<ConfigType, AuthType>>
|
|
25
|
+
): SlateAuthenticationMethod => ({
|
|
26
|
+
id: m.key,
|
|
27
|
+
name: m.name,
|
|
28
|
+
type: m.type,
|
|
29
|
+
|
|
30
|
+
scopes:
|
|
31
|
+
'scopes' in m
|
|
32
|
+
? m.scopes.map(s => ({
|
|
33
|
+
id: s.scope,
|
|
34
|
+
title: s.title,
|
|
35
|
+
description: s.description
|
|
36
|
+
}))
|
|
37
|
+
: undefined,
|
|
38
|
+
|
|
39
|
+
inputSchema: (m.inputSchema ?? z.object({})).toJSONSchema(),
|
|
40
|
+
outputSchema: slate.spec.auth.outputSchema.toJSONSchema(),
|
|
41
|
+
|
|
42
|
+
capabilities: {
|
|
43
|
+
getDefaultInput: { enabled: !!('getDefaultInput' in m && m.getDefaultInput) },
|
|
44
|
+
handleTokenRefresh: {
|
|
45
|
+
enabled: !!('handleTokenRefresh' in m && m.handleTokenRefresh)
|
|
46
|
+
},
|
|
47
|
+
handleChangedInput: {
|
|
48
|
+
enabled: !!m.onInputChanged
|
|
49
|
+
},
|
|
50
|
+
getProfile: { enabled: !!m.getProfile }
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export let getAction = <ConfigType extends {}, AuthType extends {}>(
|
|
55
|
+
slate: Slate<ConfigType, AuthType>,
|
|
56
|
+
actionId: string
|
|
57
|
+
) => {
|
|
58
|
+
let action = slate.actions.find(m => m.key == actionId);
|
|
59
|
+
if (!action) {
|
|
60
|
+
throw new ServiceError(notFoundError(`action`, actionId));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return action;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export let getActionWithType = <
|
|
67
|
+
Type extends 'tool' | 'trigger',
|
|
68
|
+
ConfigType extends {},
|
|
69
|
+
AuthType extends {}
|
|
70
|
+
>(
|
|
71
|
+
slate: Slate<ConfigType, AuthType>,
|
|
72
|
+
type: Type,
|
|
73
|
+
actionId: string
|
|
74
|
+
): ReturnType<typeof getAction<ConfigType, AuthType>> & { type: Type } => {
|
|
75
|
+
let action = getAction(slate, actionId);
|
|
76
|
+
if (action.type != type) {
|
|
77
|
+
throw new ServiceError(
|
|
78
|
+
badRequestError({
|
|
79
|
+
message: `Action with ID ${actionId} is not of type ${type}`
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return action as any;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export let mapAction = <ConfigType extends {}, AuthType extends {}>(
|
|
88
|
+
slate: Slate<ConfigType, AuthType>,
|
|
89
|
+
a: ReturnType<typeof getAction<ConfigType, AuthType>>
|
|
90
|
+
): SlatesAction => {
|
|
91
|
+
let base = {
|
|
92
|
+
id: a.key,
|
|
93
|
+
name: a.name,
|
|
94
|
+
description: a.description,
|
|
95
|
+
instructions: a.instructions,
|
|
96
|
+
constraints: a.constraints,
|
|
97
|
+
tags: a.tags,
|
|
98
|
+
metadata: a.metadata,
|
|
99
|
+
|
|
100
|
+
inputSchema: a.inputSchema.toJSONSchema(),
|
|
101
|
+
outputSchema: a.outputSchema.toJSONSchema()
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (a.type == 'tool') {
|
|
105
|
+
return {
|
|
106
|
+
...base,
|
|
107
|
+
type: 'action.tool',
|
|
108
|
+
capabilities: {}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
...base,
|
|
114
|
+
type: 'action.trigger',
|
|
115
|
+
capabilities: {},
|
|
116
|
+
|
|
117
|
+
invocation:
|
|
118
|
+
a.source == 'polling'
|
|
119
|
+
? {
|
|
120
|
+
type: 'polling',
|
|
121
|
+
intervalSeconds: a.polling.intervalInSeconds ?? SlateDefaultPollingIntervalSeconds
|
|
122
|
+
}
|
|
123
|
+
: {
|
|
124
|
+
type: 'webhook',
|
|
125
|
+
autoRegistration: !!a.autoRegisterWebhook,
|
|
126
|
+
autoUnregistration: !!a.autoUnregisterWebhook
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
};
|
package/src/state.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class State<T> {
|
|
2
|
+
#value: T;
|
|
3
|
+
|
|
4
|
+
get value() {
|
|
5
|
+
return this.#value;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get() {
|
|
9
|
+
return this.#value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
set(value: T) {
|
|
13
|
+
this.#value = value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor(initialValue: T) {
|
|
17
|
+
this.#value = initialValue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ServiceError, validationError } from '@lowerdeck/error';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
|
|
4
|
+
export let zodToValidationError = (entity: string, message: string, e: z.ZodError) => {
|
|
5
|
+
return validationError({
|
|
6
|
+
message,
|
|
7
|
+
entity,
|
|
8
|
+
errors: e.issues.map(i => ({
|
|
9
|
+
...i,
|
|
10
|
+
path: i.path.map(p => String(p))
|
|
11
|
+
}))
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export let validate = <T>(
|
|
16
|
+
schema: z.ZodType<T>,
|
|
17
|
+
data: unknown,
|
|
18
|
+
entity: string,
|
|
19
|
+
message: string
|
|
20
|
+
): T => {
|
|
21
|
+
let result = schema.safeParse(data);
|
|
22
|
+
if (!result.success) {
|
|
23
|
+
throw new ServiceError(zodToValidationError(entity, message, result.error));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return result.data;
|
|
27
|
+
};
|