@slates/provider 1.0.0-rc.17 → 1.0.0-rc.18

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.
@@ -10,6 +10,7 @@ import type {
10
10
  SlateActionScopes,
11
11
  SlateActionType,
12
12
  SlatePollingOptions,
13
+ SlatePublicToolInvocationHandler,
13
14
  SlateToolInvocationHandler,
14
15
  SlateTriggerMappingHandler,
15
16
  SlateTriggerPollingHandler,
@@ -26,7 +27,8 @@ export class SlateActionBuilder<
26
27
  AuthType extends {},
27
28
  InputType extends {},
28
29
  OutputType extends {},
29
- Result extends SlateAction<Type, ConfigType, AuthType, any, any>
30
+ Result extends SlateAction<Type, ConfigType, AuthType, any, any>,
31
+ IsPublic extends boolean = false
30
32
  > {
31
33
  #configSchema: z.ZodType<ConfigType>;
32
34
  #authSchema: z.ZodType<AuthType>;
@@ -43,6 +45,7 @@ export class SlateActionBuilder<
43
45
  InputType,
44
46
  OutputType
45
47
  > | null = null;
48
+ #interfaceLocked = false;
46
49
 
47
50
  constructor(
48
51
  private readonly type: Type,
@@ -50,7 +53,8 @@ export class SlateActionBuilder<
50
53
  private readonly params: SlateActionParameters,
51
54
  private readonly factory: (
52
55
  params: SlateActionCreateParameters<any, any, any, any>
53
- ) => Result
56
+ ) => Result,
57
+ private readonly isPublicTool: IsPublic = false as IsPublic
54
58
  ) {
55
59
  this.#configSchema = spec.configSchema;
56
60
  this.#authSchema = spec.authSchema;
@@ -58,21 +62,58 @@ export class SlateActionBuilder<
58
62
 
59
63
  input<NewInputType extends {}>(
60
64
  schema: z.ZodType<NewInputType>
61
- ): SlateActionBuilder<Type, ConfigType, AuthType, NewInputType, OutputType, Result> {
65
+ ): SlateActionBuilder<
66
+ Type,
67
+ ConfigType,
68
+ AuthType,
69
+ NewInputType,
70
+ OutputType,
71
+ Result,
72
+ IsPublic
73
+ > {
74
+ if (this.#interfaceLocked) {
75
+ throw new SlateDeclarationError('Adapter contract input schema cannot be changed');
76
+ }
77
+
62
78
  this.#inputSchema = schema as any;
63
79
  return this as any;
64
80
  }
65
81
 
66
82
  output<NewOutputType extends {}>(
67
83
  schema: z.ZodType<NewOutputType>
68
- ): SlateActionBuilder<Type, ConfigType, AuthType, InputType, NewOutputType, Result> {
84
+ ): SlateActionBuilder<
85
+ Type,
86
+ ConfigType,
87
+ AuthType,
88
+ InputType,
89
+ NewOutputType,
90
+ Result,
91
+ IsPublic
92
+ > {
93
+ if (this.#interfaceLocked) {
94
+ throw new SlateDeclarationError('Adapter contract output schema cannot be changed');
95
+ }
96
+
69
97
  this.#outputSchema = schema as any;
70
98
  return this as any;
71
99
  }
72
100
 
101
+ lockInterface(): SlateActionBuilder<
102
+ Type,
103
+ ConfigType,
104
+ AuthType,
105
+ InputType,
106
+ OutputType,
107
+ Result,
108
+ IsPublic
109
+ > {
110
+ this.#interfaceLocked = true;
111
+ return this;
112
+ }
113
+
73
114
  scopes(
74
115
  scopes: SlateActionScopes
75
- ): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result> {
116
+ ): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
76
117
  validateScopes(scopes);
77
118
  this.#scopes = scopes;
78
119
  return this;
@@ -80,7 +121,11 @@ export class SlateActionBuilder<
80
121
 
81
122
  authMethods(
82
123
  authMethods: string[]
83
- ): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result> {
124
+ ): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
125
+ if (this.isPublicTool) {
126
+ throw new SlateDeclarationError('Public tools cannot require authentication methods');
127
+ }
128
+
84
129
  this.#authMethods = this.validateAuthMethods(authMethods);
85
130
  return this;
86
131
  }
@@ -97,15 +142,22 @@ export class SlateActionBuilder<
97
142
  }
98
143
 
99
144
  handleInvocation(
100
- handler: SlateToolInvocationHandler<ConfigType, AuthType, InputType, OutputType>
101
- ): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result> {
145
+ handler: IsPublic extends true
146
+ ? SlatePublicToolInvocationHandler<InputType, OutputType>
147
+ : SlateToolInvocationHandler<ConfigType, AuthType, InputType, OutputType>
148
+ ): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
102
149
  if (this.type !== 'tool') {
103
150
  throw new SlateDeclarationError('handleInvocation can only be set for tool actions');
104
151
  }
105
152
 
106
153
  this.#toolParams = {
107
154
  type: 'tool',
108
- handleInvocation: handler
155
+ handleInvocation: handler as SlateToolInvocationHandler<
156
+ ConfigType,
157
+ AuthType,
158
+ InputType,
159
+ OutputType
160
+ >
109
161
  };
110
162
 
111
163
  return this;
@@ -117,7 +169,7 @@ export class SlateActionBuilder<
117
169
  autoRegisterWebhook?: SlateTriggerWebhookAutoRegistrationHandler<ConfigType, AuthType>;
118
170
  autoUnregisterWebhook?: SlateTriggerWebhookAutoUnregistrationHandler<ConfigType, AuthType>;
119
171
  http?: SlateWebhookHttpOptions;
120
- }): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result> {
172
+ }): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
121
173
  if (this.type !== 'trigger') {
122
174
  throw new SlateDeclarationError('handleEvent can only be set for trigger actions');
123
175
  }
@@ -139,7 +191,7 @@ export class SlateActionBuilder<
139
191
  options?: SlatePollingOptions;
140
192
  pollEvents?: SlateTriggerPollingHandler<ConfigType, AuthType, InputType>;
141
193
  handleEvent: SlateTriggerMappingHandler<ConfigType, AuthType, InputType, OutputType>;
142
- }): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result> {
194
+ }): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
143
195
  if (this.type !== 'trigger') {
144
196
  throw new SlateDeclarationError('handleEvent can only be set for trigger actions');
145
197
  }
@@ -162,6 +214,10 @@ export class SlateActionBuilder<
162
214
  }
163
215
  let authMethods = this.validateAuthMethods(this.#authMethods ?? this.params.authMethods);
164
216
 
217
+ if (this.isPublicTool && authMethods) {
218
+ throw new SlateDeclarationError('Public tools cannot require authentication methods');
219
+ }
220
+
165
221
  if (!this.#inputSchema) {
166
222
  throw new SlateDeclarationError('Input schema is not defined');
167
223
  }
@@ -179,6 +235,7 @@ export class SlateActionBuilder<
179
235
  ...this.params,
180
236
  scopes,
181
237
  authMethods,
238
+ isPublic: this.isPublicTool === true,
182
239
  configSchema: this.#configSchema,
183
240
  authSchema: this.#authSchema,
184
241
  inputSchema: this.#inputSchema,
@@ -1,5 +1,6 @@
1
1
  export * from './action';
2
- export type * from './builder';
2
+ export * from './builder';
3
+ export * from './publicTool';
3
4
  export * from './scopes';
4
5
  export * from './tool';
5
6
  export * from './trigger';
@@ -0,0 +1,36 @@
1
+ import type { SlateSpecification } from '../specification/specification';
2
+ import type { SlateActionParameters, SlatePublicToolInvocationHandler } from './action';
3
+ import { SlateActionBuilder } from './builder';
4
+ import { SlateTool } from './tool';
5
+
6
+ export interface SlatePublicToolParameters<InputType extends {}, OutputType extends {}>
7
+ extends SlateActionParameters {
8
+ handleInvocation: SlatePublicToolInvocationHandler<InputType, OutputType>;
9
+ }
10
+
11
+ export class SlatePublicTool {
12
+ private constructor() {}
13
+
14
+ static create<ConfigType extends {}, AuthType extends {}>(
15
+ spec: SlateSpecification<ConfigType, AuthType>,
16
+ params: SlateActionParameters
17
+ ) {
18
+ return new SlateActionBuilder(
19
+ 'tool',
20
+ spec,
21
+ params,
22
+ params => {
23
+ return SlateTool.fromCreateParameters(spec, {
24
+ ...params,
25
+ isPublic: true
26
+ });
27
+ },
28
+ true
29
+ );
30
+ }
31
+ }
32
+
33
+ export let publicTool = <ConfigType extends {}, AuthType extends {}>(
34
+ spec: SlateSpecification<ConfigType, AuthType>,
35
+ params: SlateActionParameters
36
+ ) => SlatePublicTool.create(spec, params);
@@ -24,7 +24,7 @@ export class SlateTool<
24
24
  > extends SlateAction<'tool', ConfigType, AuthType, InputType, OutputType> {
25
25
  #handleInvocation: SlateToolInvocationHandler<ConfigType, AuthType, InputType, OutputType>;
26
26
 
27
- private constructor(
27
+ protected constructor(
28
28
  spec: SlateSpecification<ConfigType, AuthType>,
29
29
  inputSchema: z.ZodType<InputType>,
30
30
  outputSchema: z.ZodType<OutputType>,
@@ -40,11 +40,23 @@ export class SlateTool<
40
40
  params: SlateActionParameters
41
41
  ) {
42
42
  return new SlateActionBuilder('tool', spec, params, params => {
43
- if (params.type !== 'tool') throw new Error('Invalid action type for tool');
44
- return new SlateTool(spec, params.inputSchema, params.outputSchema, params);
43
+ return SlateTool.fromCreateParameters(spec, params);
45
44
  });
46
45
  }
47
46
 
47
+ static fromCreateParameters<ConfigType extends {}, AuthType extends {}>(
48
+ spec: SlateSpecification<ConfigType, AuthType>,
49
+ params: {
50
+ type: string;
51
+ inputSchema: z.ZodType<any>;
52
+ outputSchema: z.ZodType<any>;
53
+ handleInvocation?: SlateToolInvocationHandler<ConfigType, AuthType, any, any>;
54
+ } & SlateActionParameters
55
+ ) {
56
+ if (params.type !== 'tool') throw new Error('Invalid action type for tool');
57
+ return new SlateTool(spec, params.inputSchema, params.outputSchema, params as any);
58
+ }
59
+
48
60
  get handleInvocation() {
49
61
  return this.#handleInvocation;
50
62
  }
@@ -0,0 +1,38 @@
1
+ import type { SlateTool, SlateTrigger } from '../action';
2
+ import type { SlateAdapterSpec } from './spec';
3
+
4
+ export class SlateAdapter<ConfigType extends {}, AuthType extends {}> {
5
+ constructor(
6
+ private readonly _spec: SlateAdapterSpec,
7
+ private readonly _tools: SlateTool<ConfigType, AuthType, any, any>[],
8
+ private readonly _triggers: SlateTrigger<ConfigType, AuthType, any, any>[]
9
+ ) {}
10
+
11
+ get spec() {
12
+ return this._spec;
13
+ }
14
+
15
+ get id() {
16
+ return this._spec.id;
17
+ }
18
+
19
+ get name() {
20
+ return this._spec.name;
21
+ }
22
+
23
+ get capabilities() {
24
+ return this._spec.capabilities;
25
+ }
26
+
27
+ get tools() {
28
+ return this._tools;
29
+ }
30
+
31
+ get triggers() {
32
+ return this._triggers;
33
+ }
34
+
35
+ get actions() {
36
+ return [...this._triggers, ...this._tools];
37
+ }
38
+ }
@@ -0,0 +1,322 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { z } from 'zod';
3
+ import { SlateTool } from '../action/tool';
4
+ import { SlateAuth } from '../auth';
5
+ import { SlateConfig } from '../config';
6
+ import { Slate } from '../specification/slate';
7
+ import { SlateSpecification } from '../specification/specification';
8
+ import { SlateAdapterSpec } from './spec';
9
+
10
+ let createTestSpec = () => {
11
+ let config = SlateConfig.create(z.object({}));
12
+ let auth = SlateAuth.create<{}>().output(z.object({}));
13
+
14
+ return SlateSpecification.create({
15
+ key: 'demo',
16
+ name: 'Demo',
17
+ config,
18
+ auth
19
+ });
20
+ };
21
+
22
+ describe('SlateAdapterSpec', () => {
23
+ it('creates adapter tools and triggers linked to the adapter, then registers them', () => {
24
+ let spec = createTestSpec();
25
+ let gmailAdapterSpec = SlateAdapterSpec.create({
26
+ id: 'gmail',
27
+ name: 'Gmail',
28
+ capabilities: [{ id: 'send_email', value: true }]
29
+ });
30
+
31
+ let sendEmailTool = gmailAdapterSpec
32
+ .tool(spec, {
33
+ key: 'send_email',
34
+ name: 'Send Email'
35
+ })
36
+ .input(z.object({ to: z.string() }))
37
+ .output(z.object({ id: z.string() }))
38
+ .handleInvocation(async () => ({
39
+ output: { id: 'msg_1' },
40
+ message: 'sent'
41
+ }))
42
+ .build();
43
+
44
+ let emailReceivedTrigger = gmailAdapterSpec
45
+ .trigger(spec, {
46
+ key: 'email_received',
47
+ name: 'Email Received'
48
+ })
49
+ .input(z.object({ messageId: z.string() }))
50
+ .output(z.object({ type: z.literal('email.received') }))
51
+ .polling({
52
+ handleEvent: async ctx => ({
53
+ type: 'email.received',
54
+ id: ctx.input.messageId,
55
+ output: { type: 'email.received' as const }
56
+ })
57
+ })
58
+ .build();
59
+
60
+ expect(sendEmailTool.adapter).toBe('gmail');
61
+ expect(emailReceivedTrigger.adapter).toBe('gmail');
62
+
63
+ let gmailAdapter = gmailAdapterSpec.register({
64
+ tools: [sendEmailTool],
65
+ triggers: [emailReceivedTrigger]
66
+ });
67
+
68
+ expect(gmailAdapter.id).toBe('gmail');
69
+ expect(gmailAdapter.name).toBe('Gmail');
70
+ expect(gmailAdapter.capabilities).toEqual([{ id: 'send_email', value: true }]);
71
+ expect(gmailAdapter.tools).toEqual([sendEmailTool]);
72
+ expect(gmailAdapter.triggers).toEqual([emailReceivedTrigger]);
73
+
74
+ let providerTool = SlateTool.create(spec, {
75
+ key: 'list_labels',
76
+ name: 'List Labels'
77
+ })
78
+ .input(z.object({}))
79
+ .output(z.object({ labels: z.array(z.string()) }))
80
+ .handleInvocation(async () => ({
81
+ output: { labels: [] },
82
+ message: 'ok'
83
+ }))
84
+ .build();
85
+
86
+ let slate = Slate.create({
87
+ spec,
88
+ tools: [providerTool, sendEmailTool],
89
+ triggers: [emailReceivedTrigger],
90
+ adapters: [gmailAdapter]
91
+ });
92
+
93
+ expect(slate.actions.map(action => action.key)).toEqual([
94
+ 'email_received',
95
+ 'list_labels',
96
+ 'send_email'
97
+ ]);
98
+ expect(slate.adapters).toEqual([gmailAdapter]);
99
+ expect(slate.actions.filter(action => !action.adapter).map(action => action.key)).toEqual([
100
+ 'list_labels'
101
+ ]);
102
+ });
103
+
104
+ it('includes adapter actions in the slate even when they are only passed via the adapter', () => {
105
+ let spec = createTestSpec();
106
+ let gmailAdapterSpec = SlateAdapterSpec.create({
107
+ id: 'gmail',
108
+ name: 'Gmail'
109
+ });
110
+
111
+ let sendEmailTool = gmailAdapterSpec
112
+ .tool(spec, {
113
+ key: 'send_email',
114
+ name: 'Send Email'
115
+ })
116
+ .input(z.object({}))
117
+ .output(z.object({}))
118
+ .handleInvocation(async () => ({
119
+ output: {},
120
+ message: 'ok'
121
+ }))
122
+ .build();
123
+
124
+ let gmailAdapter = gmailAdapterSpec.register({
125
+ tools: [sendEmailTool],
126
+ triggers: []
127
+ });
128
+
129
+ let slate = Slate.create({
130
+ spec,
131
+ tools: [],
132
+ triggers: [],
133
+ adapters: [gmailAdapter]
134
+ });
135
+
136
+ expect(slate.actions).toEqual([sendEmailTool]);
137
+ expect(slate.actions[0]!.adapter).toBe('gmail');
138
+ });
139
+
140
+ it('rejects tools created for a different adapter', () => {
141
+ let spec = createTestSpec();
142
+ let gmailAdapterSpec = SlateAdapterSpec.create({
143
+ id: 'gmail',
144
+ name: 'Gmail'
145
+ });
146
+ let outlookAdapterSpec = SlateAdapterSpec.create({
147
+ id: 'outlook',
148
+ name: 'Outlook'
149
+ });
150
+
151
+ let outlookTool = outlookAdapterSpec
152
+ .tool(spec, {
153
+ key: 'send_email',
154
+ name: 'Send Email'
155
+ })
156
+ .input(z.object({}))
157
+ .output(z.object({}))
158
+ .handleInvocation(async () => ({
159
+ output: {},
160
+ message: 'ok'
161
+ }))
162
+ .build();
163
+
164
+ expect(() =>
165
+ gmailAdapterSpec.register({
166
+ tools: [outlookTool],
167
+ triggers: []
168
+ })
169
+ ).toThrow('Action "send_email" is linked to adapter "outlook", not "gmail"');
170
+ });
171
+
172
+ it('rejects provider tools that were not created through the adapter helpers', () => {
173
+ let spec = createTestSpec();
174
+ let gmailAdapterSpec = SlateAdapterSpec.create({
175
+ id: 'gmail',
176
+ name: 'Gmail'
177
+ });
178
+
179
+ let providerTool = SlateTool.create(spec, {
180
+ key: 'send_email',
181
+ name: 'Send Email'
182
+ })
183
+ .input(z.object({}))
184
+ .output(z.object({}))
185
+ .handleInvocation(async () => ({
186
+ output: {},
187
+ message: 'ok'
188
+ }))
189
+ .build();
190
+
191
+ expect(() =>
192
+ gmailAdapterSpec.register({
193
+ tools: [providerTool],
194
+ triggers: []
195
+ })
196
+ ).toThrow(
197
+ 'Action "send_email" must be created with this adapter\'s tool(), publicTool(), or trigger() helpers'
198
+ );
199
+ });
200
+
201
+ it('rejects duplicate adapter action keys and duplicate adapter ids', () => {
202
+ let spec = createTestSpec();
203
+ let gmailAdapterSpec = SlateAdapterSpec.create({
204
+ id: 'gmail',
205
+ name: 'Gmail'
206
+ });
207
+
208
+ let sendEmailTool = gmailAdapterSpec
209
+ .tool(spec, {
210
+ key: 'send_email',
211
+ name: 'Send Email'
212
+ })
213
+ .input(z.object({}))
214
+ .output(z.object({}))
215
+ .handleInvocation(async () => ({
216
+ output: {},
217
+ message: 'ok'
218
+ }))
219
+ .build();
220
+
221
+ let sendEmailTrigger = gmailAdapterSpec
222
+ .trigger(spec, {
223
+ key: 'send_email',
224
+ name: 'Send Email Trigger'
225
+ })
226
+ .input(z.object({}))
227
+ .output(z.object({ type: z.string() }))
228
+ .polling({
229
+ handleEvent: async () => ({
230
+ type: 'email.sent',
231
+ id: '1',
232
+ output: { type: 'email.sent' }
233
+ })
234
+ })
235
+ .build();
236
+
237
+ expect(() =>
238
+ gmailAdapterSpec.register({
239
+ tools: [sendEmailTool],
240
+ triggers: [sendEmailTrigger]
241
+ })
242
+ ).toThrow('Adapter "gmail" already has an action with key "send_email"');
243
+
244
+ let adapter = gmailAdapterSpec.register({
245
+ tools: [sendEmailTool],
246
+ triggers: []
247
+ });
248
+
249
+ expect(() =>
250
+ Slate.create({
251
+ spec,
252
+ tools: [],
253
+ triggers: [],
254
+ adapters: [adapter, adapter]
255
+ })
256
+ ).toThrow('Adapter "gmail" is registered more than once');
257
+ });
258
+
259
+ it('rejects empty adapter ids and duplicate capability ids', () => {
260
+ expect(() =>
261
+ SlateAdapterSpec.create({
262
+ id: ' ',
263
+ name: 'Gmail'
264
+ })
265
+ ).toThrow('Adapter id must be a non-empty string');
266
+
267
+ expect(() =>
268
+ SlateAdapterSpec.create({
269
+ id: 'gmail',
270
+ name: 'Gmail',
271
+ capabilities: [
272
+ { id: 'send_email', value: true },
273
+ { id: 'send_email', value: false }
274
+ ]
275
+ })
276
+ ).toThrow('Adapter capability "send_email" is defined more than once');
277
+ });
278
+
279
+ it('creates public adapter tools that remain linked and public', () => {
280
+ let spec = createTestSpec();
281
+ let gmailAdapterSpec = SlateAdapterSpec.create({
282
+ id: 'gmail',
283
+ name: 'Gmail'
284
+ });
285
+
286
+ let setupTool = gmailAdapterSpec
287
+ .publicTool(spec, {
288
+ key: 'setup',
289
+ name: 'Setup'
290
+ })
291
+ .input(z.object({}))
292
+ .output(z.object({ ok: z.boolean() }))
293
+ .handleInvocation(async ctx => {
294
+ expect(ctx).not.toHaveProperty('config');
295
+ expect(ctx).not.toHaveProperty('auth');
296
+ return {
297
+ output: { ok: true },
298
+ message: 'ok'
299
+ };
300
+ })
301
+ .build();
302
+
303
+ expect(setupTool.adapter).toBe('gmail');
304
+ expect(setupTool.isPublic).toBe(true);
305
+
306
+ let gmailAdapter = gmailAdapterSpec.register({
307
+ tools: [setupTool],
308
+ triggers: []
309
+ });
310
+
311
+ let slate = Slate.create({
312
+ spec,
313
+ tools: [setupTool],
314
+ triggers: [],
315
+ adapters: [gmailAdapter]
316
+ });
317
+
318
+ expect(slate.actions.map(action => action.key)).toEqual(['setup']);
319
+ expect(slate.actions[0]!.isPublic).toBe(true);
320
+ expect(slate.actions[0]!.adapter).toBe('gmail');
321
+ });
322
+ });
@@ -0,0 +1,2 @@
1
+ export * from './adapter';
2
+ export * from './spec';