@slates/provider 1.0.0-rc.2 → 1.0.0-rc.21
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/dist/index.cjs +2885 -2
- package/dist/index.d.cts +1019 -0
- package/dist/index.d.ts +1019 -9
- package/dist/index.module.js +2775 -2
- package/package.json +9 -6
- package/src/action/action.ts +92 -4
- package/src/action/attachment.ts +87 -0
- package/src/action/builder.test.ts +136 -0
- package/src/action/builder.ts +113 -15
- package/src/action/directUpload.test.ts +70 -0
- package/src/action/directUpload.ts +105 -0
- package/src/action/getFileUrlTool.ts +41 -0
- package/src/action/index.ts +4 -2
- package/src/action/publicTool.ts +36 -0
- package/src/action/scopes.test.ts +76 -0
- package/src/action/scopes.ts +103 -0
- package/src/action/tool.ts +22 -6
- package/src/action/trigger.ts +19 -11
- package/src/adapter/adapter.ts +38 -0
- package/src/adapter/index.test.ts +322 -0
- package/src/adapter/index.ts +2 -0
- package/src/adapter/spec.ts +154 -0
- package/src/auth/auth.ts +3 -3
- package/src/auth/index.ts +2 -0
- package/src/auth/oauth.test.ts +83 -0
- package/src/auth/oauth.ts +150 -0
- package/src/auth/redact.test.ts +103 -0
- package/src/auth/redact.ts +111 -0
- package/src/auth/types.ts +72 -13
- package/src/axios/http.test.ts +83 -0
- package/src/axios/index.ts +116 -10
- package/src/axios/trace.test.ts +377 -0
- package/src/axios/trace.ts +803 -0
- package/src/config/config.ts +14 -3
- package/src/context/context.ts +264 -17
- package/src/context/hook.test.ts +46 -0
- package/src/context/hook.ts +15 -6
- package/src/context/index.ts +1 -0
- package/src/context/pQueue.ts +4 -0
- package/src/crypto/hmac.test.ts +40 -0
- package/src/crypto/hmac.ts +32 -0
- package/src/crypto/index.ts +1 -0
- package/src/docs.ts +22 -0
- package/src/error/api.test.ts +114 -0
- package/src/error/api.ts +236 -0
- package/src/error/axios.ts +220 -0
- package/src/error/base.test.ts +178 -0
- package/src/error/base.ts +108 -2
- package/src/error/declaration.ts +7 -2
- package/src/error/defaults.ts +50 -0
- package/src/error/index.ts +4 -0
- package/src/error/service.ts +170 -0
- package/src/error/types.ts +75 -0
- package/src/error/utils.ts +157 -0
- package/src/file/index.test.ts +36 -0
- package/src/file/index.ts +27 -0
- package/src/index.ts +8 -0
- package/src/logger/logger.test.ts +57 -0
- package/src/logger/logger.ts +72 -11
- package/src/module/resolveDefaultExport.test.ts +32 -0
- package/src/module/resolveDefaultExport.ts +14 -0
- package/src/object/index.test.ts +38 -0
- package/src/object/index.ts +12 -0
- package/src/specification/slate.ts +40 -4
- package/src/specification/specification.ts +10 -6
- package/src/webhook/graph.test.ts +37 -0
- package/src/webhook/graph.ts +22 -0
- package/src/webhook/index.ts +2 -0
- package/src/webhook/meta.test.ts +52 -0
- package/src/webhook/meta.ts +43 -0
- package/dist/action/action.d.ts +0 -98
- package/dist/action/action.d.ts.map +0 -1
- package/dist/action/builder.d.ts +0 -27
- package/dist/action/builder.d.ts.map +0 -1
- package/dist/action/index.d.ts +0 -5
- package/dist/action/index.d.ts.map +0 -1
- package/dist/action/tool.d.ts +0 -14
- package/dist/action/tool.d.ts.map +0 -1
- package/dist/action/trigger.d.ts +0 -32
- package/dist/action/trigger.d.ts.map +0 -1
- package/dist/auth/auth.d.ts +0 -30
- package/dist/auth/auth.d.ts.map +0 -1
- package/dist/auth/index.d.ts +0 -3
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/types.d.ts +0 -148
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/axios/index.d.ts +0 -4
- package/dist/axios/index.d.ts.map +0 -1
- package/dist/config/config.d.ts +0 -26
- package/dist/config/config.d.ts.map +0 -1
- package/dist/config/index.d.ts +0 -2
- package/dist/config/index.d.ts.map +0 -1
- package/dist/context/context.d.ts +0 -20
- package/dist/context/context.d.ts.map +0 -1
- package/dist/context/hook.d.ts +0 -4
- package/dist/context/hook.d.ts.map +0 -1
- package/dist/context/index.d.ts +0 -2
- package/dist/context/index.d.ts.map +0 -1
- package/dist/error/base.d.ts +0 -5
- package/dist/error/base.d.ts.map +0 -1
- package/dist/error/declaration.d.ts +0 -6
- package/dist/error/declaration.d.ts.map +0 -1
- package/dist/error/index.d.ts +0 -3
- package/dist/error/index.d.ts.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.modern.js +0 -2
- package/dist/index.modern.js.map +0 -1
- package/dist/index.module.js.map +0 -1
- package/dist/index.umd.js +0 -2
- package/dist/index.umd.js.map +0 -1
- package/dist/logger/index.d.ts +0 -2
- package/dist/logger/index.d.ts.map +0 -1
- package/dist/logger/logger.d.ts +0 -20
- package/dist/logger/logger.d.ts.map +0 -1
- package/dist/specification/index.d.ts +0 -3
- package/dist/specification/index.d.ts.map +0 -1
- package/dist/specification/slate.d.ts +0 -15
- package/dist/specification/slate.d.ts.map +0 -1
- package/dist/specification/specification.d.ts +0 -30
- package/dist/specification/specification.d.ts.map +0 -1
- package/dist/specification/zero.d.ts +0 -13
- package/dist/specification/zero.d.ts.map +0 -1
- package/dist/tokens.d.ts +0 -34
- package/dist/tokens.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slates/provider",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.21",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"package.json"
|
|
12
12
|
],
|
|
13
13
|
"author": "Tobias Herber",
|
|
14
|
-
"license": "
|
|
14
|
+
"license": "FSL 1.1",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"source": "src/index.ts",
|
|
17
17
|
"exports": {
|
|
@@ -23,20 +23,23 @@
|
|
|
23
23
|
"main": "./dist/index.cjs",
|
|
24
24
|
"module": "./dist/index.module.js",
|
|
25
25
|
"types": "dist/index.d.ts",
|
|
26
|
-
"unpkg": "./dist/index.
|
|
26
|
+
"unpkg": "./dist/index.module.js",
|
|
27
27
|
"scripts": {
|
|
28
28
|
"test": "vitest run --passWithNoTests",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
29
|
+
"build": "tsup --config ../../tsup.packages.config.ts --external async_hooks",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
|
+
"biome:check": "biome check --write src"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
34
|
+
"@lowerdeck/error": "^1.1.0",
|
|
33
35
|
"@toon-format/toon": "^2.1.0",
|
|
34
36
|
"axios": "^1.13.2",
|
|
37
|
+
"p-queue": "^6.6.2",
|
|
35
38
|
"zod": "^4.2.1"
|
|
36
39
|
},
|
|
37
40
|
"devDependencies": {
|
|
38
41
|
"microbundle": "^0.15.1",
|
|
39
|
-
"@slates/tsconfig": "
|
|
42
|
+
"@slates/tsconfig": "1.0.0-rc.1",
|
|
40
43
|
"typescript": "5.8.2",
|
|
41
44
|
"vitest": "^3.1.2"
|
|
42
45
|
}
|
package/src/action/action.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { SlateContext } from '../context';
|
|
3
|
-
import {
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { SlateContext, SlatePublicContext } from '../context';
|
|
3
|
+
import type { SlateActionDocsReference } from '../docs';
|
|
4
|
+
import type { SlateSpecification } from '../specification/specification';
|
|
5
|
+
import type { SlateAttachment } from './attachment';
|
|
4
6
|
|
|
5
7
|
export type SlateActionType = 'tool' | 'trigger';
|
|
6
8
|
|
|
9
|
+
export interface SlateActionScopeClause {
|
|
10
|
+
OR: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SlateActionScopes {
|
|
14
|
+
AND: SlateActionScopeClause[];
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
export interface SlateActionParameters {
|
|
8
18
|
key: string;
|
|
9
19
|
name: string;
|
|
@@ -16,6 +26,11 @@ export interface SlateActionParameters {
|
|
|
16
26
|
[key: string]: boolean | undefined;
|
|
17
27
|
};
|
|
18
28
|
metadata?: Record<string, any>;
|
|
29
|
+
scopes?: SlateActionScopes;
|
|
30
|
+
authMethods?: string[];
|
|
31
|
+
docs?: SlateActionDocsReference[];
|
|
32
|
+
adapter?: string | null;
|
|
33
|
+
isPublic?: boolean;
|
|
19
34
|
}
|
|
20
35
|
|
|
21
36
|
export type SlateToolInvocationHandler<
|
|
@@ -26,6 +41,15 @@ export type SlateToolInvocationHandler<
|
|
|
26
41
|
> = (context: SlateContext<ConfigType, AuthType, InputType>) => Promise<{
|
|
27
42
|
output: OutputType;
|
|
28
43
|
message: string;
|
|
44
|
+
attachments?: SlateAttachment[];
|
|
45
|
+
}>;
|
|
46
|
+
|
|
47
|
+
export type SlatePublicToolInvocationHandler<InputType extends {}, OutputType extends {}> = (
|
|
48
|
+
context: SlatePublicContext<InputType>
|
|
49
|
+
) => Promise<{
|
|
50
|
+
output: OutputType;
|
|
51
|
+
message: string;
|
|
52
|
+
attachments?: SlateAttachment[];
|
|
29
53
|
}>;
|
|
30
54
|
|
|
31
55
|
export type SlateTriggerMappingHandler<
|
|
@@ -48,15 +72,58 @@ export type SlateTriggerPollingHandler<
|
|
|
48
72
|
updatedState?: any;
|
|
49
73
|
}>;
|
|
50
74
|
|
|
75
|
+
export interface SlateWebhookHttpResponseInit {
|
|
76
|
+
status?: number;
|
|
77
|
+
headers?: Record<string, string>;
|
|
78
|
+
body?: string | Uint8Array | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type SlateWebhookHttpMethod =
|
|
82
|
+
| 'GET'
|
|
83
|
+
| 'POST'
|
|
84
|
+
| 'PUT'
|
|
85
|
+
| 'PATCH'
|
|
86
|
+
| 'DELETE'
|
|
87
|
+
| 'HEAD'
|
|
88
|
+
| 'OPTIONS';
|
|
89
|
+
|
|
90
|
+
export interface SlateWebhookRequestMatcher {
|
|
91
|
+
method?: string;
|
|
92
|
+
hasQueryParam?: string;
|
|
93
|
+
hasHeader?: string;
|
|
94
|
+
jsonBodyField?: {
|
|
95
|
+
path: string;
|
|
96
|
+
equals?: string;
|
|
97
|
+
};
|
|
98
|
+
formBodyField?: {
|
|
99
|
+
path: string;
|
|
100
|
+
equals?: string;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface SlateWebhookHttpOptions {
|
|
105
|
+
methods?: SlateWebhookHttpMethod[];
|
|
106
|
+
sync?: {
|
|
107
|
+
mode: 'never' | 'match' | 'always';
|
|
108
|
+
match?: SlateWebhookRequestMatcher[];
|
|
109
|
+
timeoutMs?: number;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
51
113
|
export type SlateTriggerWebhookRequestHandler<
|
|
52
114
|
ConfigType extends {},
|
|
53
115
|
AuthType extends {},
|
|
54
116
|
InputType extends {}
|
|
55
117
|
> = (
|
|
56
|
-
context: SlateContext<
|
|
118
|
+
context: SlateContext<
|
|
119
|
+
ConfigType,
|
|
120
|
+
AuthType,
|
|
121
|
+
{ request: Request; state: any | null; registrationDetails: any | null }
|
|
122
|
+
>
|
|
57
123
|
) => Promise<{
|
|
58
124
|
inputs: InputType[];
|
|
59
125
|
updatedState?: any;
|
|
126
|
+
response?: Response | SlateWebhookHttpResponseInit;
|
|
60
127
|
}>;
|
|
61
128
|
|
|
62
129
|
export type SlateTriggerWebhookAutoRegistrationHandler<
|
|
@@ -103,6 +170,7 @@ export interface SlateActionParametersTrigger<
|
|
|
103
170
|
polling?: SlatePollingOptions;
|
|
104
171
|
handleEvent: SlateTriggerMappingHandler<ConfigType, AuthType, InputType, OutputType>;
|
|
105
172
|
handleRequest?: SlateTriggerWebhookRequestHandler<ConfigType, AuthType, InputType>;
|
|
173
|
+
http?: SlateWebhookHttpOptions;
|
|
106
174
|
pollEvents?: SlateTriggerPollingHandler<ConfigType, AuthType, InputType>;
|
|
107
175
|
autoRegisterWebhook?: SlateTriggerWebhookAutoRegistrationHandler<ConfigType, AuthType>;
|
|
108
176
|
autoUnregisterWebhook?: SlateTriggerWebhookAutoUnregistrationHandler<ConfigType, AuthType>;
|
|
@@ -188,4 +256,24 @@ export abstract class SlateAction<
|
|
|
188
256
|
get metadata() {
|
|
189
257
|
return this._params.metadata;
|
|
190
258
|
}
|
|
259
|
+
|
|
260
|
+
get scopes() {
|
|
261
|
+
return this._params.scopes;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
get authMethods() {
|
|
265
|
+
return this._params.authMethods;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
get docs() {
|
|
269
|
+
return this._params.docs;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
get adapter() {
|
|
273
|
+
return this._params.adapter ?? null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
get isPublic() {
|
|
277
|
+
return this._params.isPublic === true;
|
|
278
|
+
}
|
|
191
279
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export interface SlateAttachmentUrlContent {
|
|
2
|
+
type: 'url';
|
|
3
|
+
url: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
query?: Record<string, string>;
|
|
6
|
+
refreshReference?: unknown;
|
|
7
|
+
refreshAt?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SlateAttachmentInlineContent {
|
|
11
|
+
type: 'content';
|
|
12
|
+
encoding: 'base64' | 'utf-8';
|
|
13
|
+
content: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SlateAttachmentUploadReferenceContent {
|
|
17
|
+
type: 'upload_reference';
|
|
18
|
+
referenceId: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SlateAttachment {
|
|
22
|
+
mimeType?: string;
|
|
23
|
+
attachmentHash?: string;
|
|
24
|
+
content:
|
|
25
|
+
| SlateAttachmentUrlContent
|
|
26
|
+
| SlateAttachmentInlineContent
|
|
27
|
+
| SlateAttachmentUploadReferenceContent;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export let createUrlAttachment = (
|
|
31
|
+
url: string,
|
|
32
|
+
opts: {
|
|
33
|
+
mimeType?: string;
|
|
34
|
+
attachmentHash?: string;
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
query?: Record<string, string>;
|
|
37
|
+
refreshReference?: unknown;
|
|
38
|
+
refreshAt?: string;
|
|
39
|
+
} = {}
|
|
40
|
+
): SlateAttachment => {
|
|
41
|
+
if (opts.refreshReference !== undefined && !opts.refreshAt) {
|
|
42
|
+
throw new Error('createUrlAttachment: refreshAt is required when refreshReference is set');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
mimeType: opts.mimeType,
|
|
47
|
+
attachmentHash: opts.attachmentHash,
|
|
48
|
+
content: {
|
|
49
|
+
type: 'url',
|
|
50
|
+
url,
|
|
51
|
+
...(opts.headers ? { headers: opts.headers } : {}),
|
|
52
|
+
...(opts.query ? { query: opts.query } : {}),
|
|
53
|
+
...(opts.refreshReference !== undefined
|
|
54
|
+
? { refreshReference: opts.refreshReference }
|
|
55
|
+
: {}),
|
|
56
|
+
...(opts.refreshAt ? { refreshAt: opts.refreshAt } : {})
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export let createBase64Attachment = (
|
|
62
|
+
content: string,
|
|
63
|
+
mimeType?: string,
|
|
64
|
+
attachmentHash?: string
|
|
65
|
+
): SlateAttachment => ({
|
|
66
|
+
mimeType,
|
|
67
|
+
attachmentHash,
|
|
68
|
+
content: {
|
|
69
|
+
type: 'content',
|
|
70
|
+
encoding: 'base64',
|
|
71
|
+
content
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export let createTextAttachment = (
|
|
76
|
+
content: string,
|
|
77
|
+
mimeType?: string,
|
|
78
|
+
attachmentHash?: string
|
|
79
|
+
): SlateAttachment => ({
|
|
80
|
+
mimeType,
|
|
81
|
+
attachmentHash,
|
|
82
|
+
content: {
|
|
83
|
+
type: 'content',
|
|
84
|
+
encoding: 'utf-8',
|
|
85
|
+
content
|
|
86
|
+
}
|
|
87
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { SlateAuth } from '../auth';
|
|
4
|
+
import { SlateConfig } from '../config';
|
|
5
|
+
import { SlatePublicContext } from '../context';
|
|
6
|
+
import { SlateSpecification } from '../specification/specification';
|
|
7
|
+
import { SlatePublicTool } from './publicTool';
|
|
8
|
+
import { SlateTool } from './tool';
|
|
9
|
+
|
|
10
|
+
let createTestSpec = () => {
|
|
11
|
+
let config = SlateConfig.create(
|
|
12
|
+
z.object({
|
|
13
|
+
prefix: z.string()
|
|
14
|
+
})
|
|
15
|
+
);
|
|
16
|
+
let auth = SlateAuth.create<{ token: string }>().output(
|
|
17
|
+
z.object({
|
|
18
|
+
token: z.string()
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return SlateSpecification.create({
|
|
23
|
+
key: 'demo',
|
|
24
|
+
name: 'Demo',
|
|
25
|
+
config,
|
|
26
|
+
auth
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe('SlateActionBuilder.lockInterface', () => {
|
|
31
|
+
it('prevents changing input and output schemas after the interface is locked', () => {
|
|
32
|
+
let spec = createTestSpec();
|
|
33
|
+
let builder = SlateTool.create(spec, {
|
|
34
|
+
key: 'send',
|
|
35
|
+
name: 'Send'
|
|
36
|
+
})
|
|
37
|
+
.input(z.object({ to: z.string() }))
|
|
38
|
+
.output(z.object({ id: z.string() }))
|
|
39
|
+
.lockInterface();
|
|
40
|
+
|
|
41
|
+
expect(() => builder.input(z.object({}))).toThrow(
|
|
42
|
+
'Adapter contract input schema cannot be changed'
|
|
43
|
+
);
|
|
44
|
+
expect(() => builder.output(z.object({}))).toThrow(
|
|
45
|
+
'Adapter contract output schema cannot be changed'
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('SlatePublicTool', () => {
|
|
51
|
+
it('builds a public tool that omits config and auth from the handler context', async () => {
|
|
52
|
+
let spec = createTestSpec();
|
|
53
|
+
let tool = SlatePublicTool.create(spec, {
|
|
54
|
+
key: 'setup',
|
|
55
|
+
name: 'Setup'
|
|
56
|
+
})
|
|
57
|
+
.input(
|
|
58
|
+
z.object({
|
|
59
|
+
name: z.string()
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
.output(
|
|
63
|
+
z.object({
|
|
64
|
+
greeting: z.string()
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
.handleInvocation(async ctx => {
|
|
68
|
+
expect(ctx).not.toHaveProperty('config');
|
|
69
|
+
expect(ctx).not.toHaveProperty('auth');
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
output: { greeting: `Hello ${ctx.input.name}` },
|
|
73
|
+
message: 'ok'
|
|
74
|
+
};
|
|
75
|
+
})
|
|
76
|
+
.build();
|
|
77
|
+
|
|
78
|
+
expect(tool.isPublic).toBe(true);
|
|
79
|
+
expect(tool.key).toBe('setup');
|
|
80
|
+
|
|
81
|
+
let result = await tool.handleInvocation(
|
|
82
|
+
new SlatePublicContext({ name: 'Ada' }, spec, {
|
|
83
|
+
debug: () => {},
|
|
84
|
+
error: () => {},
|
|
85
|
+
info: () => {},
|
|
86
|
+
progress: () => {},
|
|
87
|
+
warn: () => {}
|
|
88
|
+
} as any) as any
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(result.output).toEqual({ greeting: 'Hello Ada' });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('keeps regular tools private by default', () => {
|
|
95
|
+
let spec = createTestSpec();
|
|
96
|
+
let tool = SlateTool.create(spec, {
|
|
97
|
+
key: 'send',
|
|
98
|
+
name: 'Send'
|
|
99
|
+
})
|
|
100
|
+
.input(z.object({}))
|
|
101
|
+
.output(z.object({}))
|
|
102
|
+
.handleInvocation(async () => ({
|
|
103
|
+
output: {},
|
|
104
|
+
message: 'ok'
|
|
105
|
+
}))
|
|
106
|
+
.build();
|
|
107
|
+
|
|
108
|
+
expect(tool.isPublic).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('rejects authentication methods on public tools', () => {
|
|
112
|
+
let spec = createTestSpec();
|
|
113
|
+
|
|
114
|
+
expect(() =>
|
|
115
|
+
SlatePublicTool.create(spec, {
|
|
116
|
+
key: 'setup',
|
|
117
|
+
name: 'Setup'
|
|
118
|
+
}).authMethods(['token_auth'])
|
|
119
|
+
).toThrow('Public tools cannot require authentication methods');
|
|
120
|
+
|
|
121
|
+
expect(() =>
|
|
122
|
+
SlatePublicTool.create(spec, {
|
|
123
|
+
key: 'setup',
|
|
124
|
+
name: 'Setup',
|
|
125
|
+
authMethods: ['token_auth']
|
|
126
|
+
})
|
|
127
|
+
.input(z.object({}))
|
|
128
|
+
.output(z.object({}))
|
|
129
|
+
.handleInvocation(async () => ({
|
|
130
|
+
output: {},
|
|
131
|
+
message: 'ok'
|
|
132
|
+
}))
|
|
133
|
+
.build()
|
|
134
|
+
).toThrow('Public tools cannot require authentication methods');
|
|
135
|
+
});
|
|
136
|
+
});
|
package/src/action/builder.ts
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
import z from 'zod';
|
|
1
|
+
import type z from 'zod';
|
|
2
2
|
import { SlateDeclarationError } from '../error';
|
|
3
|
-
import { SlateSpecification } from '../specification/specification';
|
|
4
|
-
import {
|
|
3
|
+
import type { SlateSpecification } from '../specification/specification';
|
|
4
|
+
import type {
|
|
5
5
|
SlateAction,
|
|
6
6
|
SlateActionCreateParameters,
|
|
7
7
|
SlateActionParameters,
|
|
8
8
|
SlateActionParametersTool,
|
|
9
9
|
SlateActionParametersTrigger,
|
|
10
|
+
SlateActionScopes,
|
|
10
11
|
SlateActionType,
|
|
11
12
|
SlatePollingOptions,
|
|
13
|
+
SlatePublicToolInvocationHandler,
|
|
12
14
|
SlateToolInvocationHandler,
|
|
13
15
|
SlateTriggerMappingHandler,
|
|
14
16
|
SlateTriggerPollingHandler,
|
|
15
17
|
SlateTriggerWebhookAutoRegistrationHandler,
|
|
16
18
|
SlateTriggerWebhookAutoUnregistrationHandler,
|
|
17
|
-
SlateTriggerWebhookRequestHandler
|
|
19
|
+
SlateTriggerWebhookRequestHandler,
|
|
20
|
+
SlateWebhookHttpOptions
|
|
18
21
|
} from './action';
|
|
22
|
+
import { validateScopes } from './scopes';
|
|
19
23
|
|
|
20
24
|
export class SlateActionBuilder<
|
|
21
25
|
Type extends SlateActionType,
|
|
@@ -23,12 +27,15 @@ export class SlateActionBuilder<
|
|
|
23
27
|
AuthType extends {},
|
|
24
28
|
InputType extends {},
|
|
25
29
|
OutputType extends {},
|
|
26
|
-
Result extends SlateAction<Type, ConfigType, AuthType, any, any
|
|
30
|
+
Result extends SlateAction<Type, ConfigType, AuthType, any, any>,
|
|
31
|
+
IsPublic extends boolean = false
|
|
27
32
|
> {
|
|
28
33
|
#configSchema: z.ZodType<ConfigType>;
|
|
29
34
|
#authSchema: z.ZodType<AuthType>;
|
|
30
35
|
#inputSchema: z.ZodType<InputType> | null = null;
|
|
31
36
|
#outputSchema: z.ZodType<OutputType> | null = null;
|
|
37
|
+
#scopes: SlateActionScopes | undefined;
|
|
38
|
+
#authMethods: string[] | undefined;
|
|
32
39
|
|
|
33
40
|
#toolParams: SlateActionParametersTool<ConfigType, AuthType, InputType, OutputType> | null =
|
|
34
41
|
null;
|
|
@@ -38,14 +45,16 @@ export class SlateActionBuilder<
|
|
|
38
45
|
InputType,
|
|
39
46
|
OutputType
|
|
40
47
|
> | null = null;
|
|
48
|
+
#interfaceLocked = false;
|
|
41
49
|
|
|
42
50
|
constructor(
|
|
43
51
|
private readonly type: Type,
|
|
44
|
-
|
|
52
|
+
readonly spec: SlateSpecification<ConfigType, AuthType>,
|
|
45
53
|
private readonly params: SlateActionParameters,
|
|
46
54
|
private readonly factory: (
|
|
47
55
|
params: SlateActionCreateParameters<any, any, any, any>
|
|
48
|
-
) => Result
|
|
56
|
+
) => Result,
|
|
57
|
+
private readonly isPublicTool: IsPublic = false as IsPublic
|
|
49
58
|
) {
|
|
50
59
|
this.#configSchema = spec.configSchema;
|
|
51
60
|
this.#authSchema = spec.authSchema;
|
|
@@ -53,28 +62,102 @@ export class SlateActionBuilder<
|
|
|
53
62
|
|
|
54
63
|
input<NewInputType extends {}>(
|
|
55
64
|
schema: z.ZodType<NewInputType>
|
|
56
|
-
): SlateActionBuilder<
|
|
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
|
+
|
|
57
78
|
this.#inputSchema = schema as any;
|
|
58
79
|
return this as any;
|
|
59
80
|
}
|
|
60
81
|
|
|
61
82
|
output<NewOutputType extends {}>(
|
|
62
83
|
schema: z.ZodType<NewOutputType>
|
|
63
|
-
): SlateActionBuilder<
|
|
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
|
+
|
|
64
97
|
this.#outputSchema = schema as any;
|
|
65
98
|
return this as any;
|
|
66
99
|
}
|
|
67
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
|
+
|
|
114
|
+
scopes(
|
|
115
|
+
scopes: SlateActionScopes
|
|
116
|
+
): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
|
|
117
|
+
validateScopes(scopes);
|
|
118
|
+
this.#scopes = scopes;
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
authMethods(
|
|
123
|
+
authMethods: string[]
|
|
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
|
+
|
|
129
|
+
this.#authMethods = this.validateAuthMethods(authMethods);
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private validateAuthMethods(authMethods: string[] | undefined) {
|
|
134
|
+
if (!authMethods) return undefined;
|
|
135
|
+
|
|
136
|
+
let normalized = [...new Set(authMethods.map(authMethod => authMethod.trim()))];
|
|
137
|
+
if (normalized.some(authMethod => !authMethod)) {
|
|
138
|
+
throw new SlateDeclarationError('Auth method IDs must be non-empty strings');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return normalized;
|
|
142
|
+
}
|
|
143
|
+
|
|
68
144
|
handleInvocation(
|
|
69
|
-
handler:
|
|
70
|
-
|
|
145
|
+
handler: IsPublic extends true
|
|
146
|
+
? SlatePublicToolInvocationHandler<InputType, OutputType>
|
|
147
|
+
: SlateToolInvocationHandler<ConfigType, AuthType, InputType, OutputType>
|
|
148
|
+
): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
|
|
71
149
|
if (this.type !== 'tool') {
|
|
72
150
|
throw new SlateDeclarationError('handleInvocation can only be set for tool actions');
|
|
73
151
|
}
|
|
74
152
|
|
|
75
153
|
this.#toolParams = {
|
|
76
154
|
type: 'tool',
|
|
77
|
-
handleInvocation: handler
|
|
155
|
+
handleInvocation: handler as SlateToolInvocationHandler<
|
|
156
|
+
ConfigType,
|
|
157
|
+
AuthType,
|
|
158
|
+
InputType,
|
|
159
|
+
OutputType
|
|
160
|
+
>
|
|
78
161
|
};
|
|
79
162
|
|
|
80
163
|
return this;
|
|
@@ -85,7 +168,8 @@ export class SlateActionBuilder<
|
|
|
85
168
|
handleRequest: SlateTriggerWebhookRequestHandler<ConfigType, AuthType, InputType>;
|
|
86
169
|
autoRegisterWebhook?: SlateTriggerWebhookAutoRegistrationHandler<ConfigType, AuthType>;
|
|
87
170
|
autoUnregisterWebhook?: SlateTriggerWebhookAutoUnregistrationHandler<ConfigType, AuthType>;
|
|
88
|
-
|
|
171
|
+
http?: SlateWebhookHttpOptions;
|
|
172
|
+
}): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
|
|
89
173
|
if (this.type !== 'trigger') {
|
|
90
174
|
throw new SlateDeclarationError('handleEvent can only be set for trigger actions');
|
|
91
175
|
}
|
|
@@ -96,7 +180,8 @@ export class SlateActionBuilder<
|
|
|
96
180
|
handleEvent: props.handleEvent,
|
|
97
181
|
handleRequest: props.handleRequest,
|
|
98
182
|
autoRegisterWebhook: props.autoRegisterWebhook,
|
|
99
|
-
autoUnregisterWebhook: props.autoUnregisterWebhook
|
|
183
|
+
autoUnregisterWebhook: props.autoUnregisterWebhook,
|
|
184
|
+
http: props.http
|
|
100
185
|
};
|
|
101
186
|
|
|
102
187
|
return this;
|
|
@@ -106,7 +191,7 @@ export class SlateActionBuilder<
|
|
|
106
191
|
options?: SlatePollingOptions;
|
|
107
192
|
pollEvents?: SlateTriggerPollingHandler<ConfigType, AuthType, InputType>;
|
|
108
193
|
handleEvent: SlateTriggerMappingHandler<ConfigType, AuthType, InputType, OutputType>;
|
|
109
|
-
}): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result> {
|
|
194
|
+
}): SlateActionBuilder<Type, ConfigType, AuthType, InputType, OutputType, Result, IsPublic> {
|
|
110
195
|
if (this.type !== 'trigger') {
|
|
111
196
|
throw new SlateDeclarationError('handleEvent can only be set for trigger actions');
|
|
112
197
|
}
|
|
@@ -123,6 +208,16 @@ export class SlateActionBuilder<
|
|
|
123
208
|
}
|
|
124
209
|
|
|
125
210
|
build() {
|
|
211
|
+
let scopes = this.#scopes ?? this.params.scopes;
|
|
212
|
+
if (scopes) {
|
|
213
|
+
validateScopes(scopes);
|
|
214
|
+
}
|
|
215
|
+
let authMethods = this.validateAuthMethods(this.#authMethods ?? this.params.authMethods);
|
|
216
|
+
|
|
217
|
+
if (this.isPublicTool && authMethods) {
|
|
218
|
+
throw new SlateDeclarationError('Public tools cannot require authentication methods');
|
|
219
|
+
}
|
|
220
|
+
|
|
126
221
|
if (!this.#inputSchema) {
|
|
127
222
|
throw new SlateDeclarationError('Input schema is not defined');
|
|
128
223
|
}
|
|
@@ -138,6 +233,9 @@ export class SlateActionBuilder<
|
|
|
138
233
|
|
|
139
234
|
return this.factory({
|
|
140
235
|
...this.params,
|
|
236
|
+
scopes,
|
|
237
|
+
authMethods,
|
|
238
|
+
isPublic: this.isPublicTool === true,
|
|
141
239
|
configSchema: this.#configSchema,
|
|
142
240
|
authSchema: this.#authSchema,
|
|
143
241
|
inputSchema: this.#inputSchema,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { uploadAttachmentDirect } from './directUpload';
|
|
3
|
+
|
|
4
|
+
let live = {
|
|
5
|
+
token: 'live-token',
|
|
6
|
+
baseUrl: 'https://hub.example/',
|
|
7
|
+
maxAttachmentSizeBytes: 10
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
describe('uploadAttachmentDirect', () => {
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.restoreAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('buffers streams and uploads them with their exact content length', async () => {
|
|
16
|
+
let fetchSpy = vi
|
|
17
|
+
.spyOn(globalThis, 'fetch')
|
|
18
|
+
.mockResolvedValueOnce(
|
|
19
|
+
Response.json({
|
|
20
|
+
attachments: [
|
|
21
|
+
{ referenceId: 'attachment-reference', uploadUrl: 'https://s3.example/upload' }
|
|
22
|
+
]
|
|
23
|
+
})
|
|
24
|
+
)
|
|
25
|
+
.mockResolvedValueOnce(new Response(null, { status: 200 }));
|
|
26
|
+
let body = new ReadableStream<Uint8Array>({
|
|
27
|
+
start(controller) {
|
|
28
|
+
controller.enqueue(new Uint8Array([1, 2]));
|
|
29
|
+
controller.enqueue(new Uint8Array([3, 4, 5]));
|
|
30
|
+
controller.close();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await expect(
|
|
35
|
+
uploadAttachmentDirect({ live, body, mimeType: 'application/octet-stream' })
|
|
36
|
+
).resolves.toEqual({
|
|
37
|
+
mimeType: 'application/octet-stream',
|
|
38
|
+
content: { type: 'upload_reference', referenceId: 'attachment-reference' }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(JSON.parse(fetchSpy.mock.calls[0]![1]!.body as string)).toEqual({
|
|
42
|
+
attachments: [{ mimeType: 'application/octet-stream', sizeBytes: 5 }]
|
|
43
|
+
});
|
|
44
|
+
let upload = fetchSpy.mock.calls[1]!;
|
|
45
|
+
expect(upload[0]).toBe('https://s3.example/upload');
|
|
46
|
+
expect(upload[1]).toMatchObject({
|
|
47
|
+
method: 'PUT',
|
|
48
|
+
headers: {
|
|
49
|
+
'content-type': 'application/octet-stream',
|
|
50
|
+
'content-length': '5'
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
expect(upload[1]!.body).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
|
|
54
|
+
expect(upload[1]).not.toHaveProperty('duplex');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('rejects an oversized stream before registering an upload', async () => {
|
|
58
|
+
let fetchSpy = vi.spyOn(globalThis, 'fetch');
|
|
59
|
+
let body = new ReadableStream<Uint8Array>({
|
|
60
|
+
start(controller) {
|
|
61
|
+
controller.enqueue(new Uint8Array(11));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await expect(uploadAttachmentDirect({ live, body })).rejects.toThrow(
|
|
66
|
+
'Attachment exceeds the maximum size of 10 bytes'
|
|
67
|
+
);
|
|
68
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
});
|