@optimizely-opal/opal-tools-sdk 0.1.3-dev → 0.1.6-dev
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/.prettierignore +5 -0
- package/.prettierrc +1 -0
- package/README.md +227 -198
- package/dist/auth.d.ts +5 -5
- package/dist/auth.js +2 -2
- package/dist/block.d.ts +4760 -0
- package/dist/block.js +104 -0
- package/dist/decorators.d.ts +8 -8
- package/dist/decorators.js +3 -43
- package/dist/index.d.ts +9 -5
- package/dist/index.js +10 -5
- package/dist/models.d.ts +125 -118
- package/dist/models.js +88 -80
- package/dist/registerTool.d.ts +68 -0
- package/dist/registerTool.js +57 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +1 -1
- package/dist/service.d.ts +10 -8
- package/dist/service.js +50 -22
- package/eslint.config.js +20 -0
- package/package.json +21 -12
- package/scripts/generate-block.ts +167 -0
- package/scripts/lint.sh +7 -0
- package/src/auth.ts +21 -16
- package/src/block.ts +11761 -0
- package/src/decorators.ts +28 -67
- package/src/index.ts +9 -5
- package/src/models.ts +117 -105
- package/src/registerTool.ts +181 -0
- package/src/registry.ts +2 -2
- package/src/service.ts +80 -37
- package/tests/block.test.ts +115 -0
- package/tests/integration.test.ts +318 -0
- package/tsconfig.build.json +5 -0
- package/tsconfig.json +3 -3
- package/vitest.config.ts +7 -0
package/dist/models.js
CHANGED
|
@@ -1,48 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.Parameter = exports.IslandResponse = exports.IslandResponseConfig = exports.IslandConfig = exports.IslandField = exports.IslandAction = exports.Function = exports.AuthRequirement = exports.ParameterType = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Types of parameters supported by Opal tools
|
|
6
6
|
*/
|
|
7
7
|
var ParameterType;
|
|
8
8
|
(function (ParameterType) {
|
|
9
|
-
ParameterType["String"] = "string";
|
|
10
|
-
ParameterType["Integer"] = "integer";
|
|
11
|
-
ParameterType["Number"] = "number";
|
|
12
9
|
ParameterType["Boolean"] = "boolean";
|
|
13
|
-
ParameterType["List"] = "array";
|
|
14
10
|
ParameterType["Dictionary"] = "object";
|
|
11
|
+
ParameterType["Integer"] = "integer";
|
|
12
|
+
ParameterType["List"] = "array";
|
|
13
|
+
ParameterType["Number"] = "number";
|
|
14
|
+
ParameterType["String"] = "string";
|
|
15
15
|
})(ParameterType || (exports.ParameterType = ParameterType = {}));
|
|
16
|
-
/**
|
|
17
|
-
* Parameter definition for an Opal tool
|
|
18
|
-
*/
|
|
19
|
-
class Parameter {
|
|
20
|
-
/**
|
|
21
|
-
* Create a new parameter definition
|
|
22
|
-
* @param name Parameter name
|
|
23
|
-
* @param type Parameter type
|
|
24
|
-
* @param description Parameter description
|
|
25
|
-
* @param required Whether the parameter is required
|
|
26
|
-
*/
|
|
27
|
-
constructor(name, type, description, required) {
|
|
28
|
-
this.name = name;
|
|
29
|
-
this.type = type;
|
|
30
|
-
this.description = description;
|
|
31
|
-
this.required = required;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Convert to JSON for the discovery endpoint
|
|
35
|
-
*/
|
|
36
|
-
toJSON() {
|
|
37
|
-
return {
|
|
38
|
-
name: this.name,
|
|
39
|
-
type: this.type,
|
|
40
|
-
description: this.description,
|
|
41
|
-
required: this.required
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
exports.Parameter = Parameter;
|
|
46
16
|
/**
|
|
47
17
|
* Authentication requirements for an Opal tool
|
|
48
18
|
*/
|
|
@@ -64,8 +34,8 @@ class AuthRequirement {
|
|
|
64
34
|
toJSON() {
|
|
65
35
|
return {
|
|
66
36
|
provider: this.provider,
|
|
37
|
+
required: this.required,
|
|
67
38
|
scope_bundle: this.scopeBundle,
|
|
68
|
-
required: this.required
|
|
69
39
|
};
|
|
70
40
|
}
|
|
71
41
|
}
|
|
@@ -91,95 +61,96 @@ class Function {
|
|
|
91
61
|
/**
|
|
92
62
|
* HTTP method for the endpoint (default: POST)
|
|
93
63
|
*/
|
|
94
|
-
this.httpMethod =
|
|
64
|
+
this.httpMethod = "POST";
|
|
95
65
|
}
|
|
96
66
|
/**
|
|
97
67
|
* Convert to JSON for the discovery endpoint
|
|
98
68
|
*/
|
|
99
69
|
toJSON() {
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
71
|
const result = {
|
|
101
|
-
name: this.name,
|
|
102
72
|
description: this.description,
|
|
103
|
-
parameters: this.parameters.map(p => p.toJSON()),
|
|
104
73
|
endpoint: this.endpoint,
|
|
105
|
-
http_method: this.httpMethod
|
|
74
|
+
http_method: this.httpMethod,
|
|
75
|
+
name: this.name,
|
|
76
|
+
parameters: this.parameters.map((p) => p.toJSON()),
|
|
106
77
|
};
|
|
107
78
|
if (this.authRequirements && this.authRequirements.length > 0) {
|
|
108
|
-
result.auth_requirements = this.authRequirements.map(auth => auth.toJSON());
|
|
79
|
+
result.auth_requirements = this.authRequirements.map((auth) => auth.toJSON());
|
|
109
80
|
}
|
|
110
81
|
return result;
|
|
111
82
|
}
|
|
112
83
|
}
|
|
113
84
|
exports.Function = Function;
|
|
114
85
|
/**
|
|
115
|
-
* Island
|
|
86
|
+
* Island action definition for interactive UI components
|
|
116
87
|
*/
|
|
117
|
-
class
|
|
88
|
+
class IslandAction {
|
|
118
89
|
/**
|
|
119
|
-
* Create a new island
|
|
120
|
-
* @param name Programmatic
|
|
121
|
-
* @param label Human-readable label
|
|
122
|
-
* @param type
|
|
123
|
-
* @param
|
|
124
|
-
* @param
|
|
125
|
-
* @param options Available options for selection
|
|
90
|
+
* Create a new island action
|
|
91
|
+
* @param name Programmatic action identifier
|
|
92
|
+
* @param label Human-readable button label
|
|
93
|
+
* @param type UI element type
|
|
94
|
+
* @param endpoint API endpoint to call
|
|
95
|
+
* @param operation Operation type
|
|
126
96
|
*/
|
|
127
|
-
constructor(name, label, type,
|
|
97
|
+
constructor(name, label, type, endpoint, operation = "create") {
|
|
128
98
|
this.name = name;
|
|
129
99
|
this.label = label;
|
|
130
100
|
this.type = type;
|
|
131
|
-
this.
|
|
132
|
-
this.
|
|
133
|
-
this.options = options;
|
|
101
|
+
this.endpoint = endpoint;
|
|
102
|
+
this.operation = operation;
|
|
134
103
|
}
|
|
135
104
|
/**
|
|
136
105
|
* Convert to JSON for the discovery endpoint
|
|
137
106
|
*/
|
|
138
107
|
toJSON() {
|
|
139
108
|
return {
|
|
140
|
-
|
|
109
|
+
endpoint: this.endpoint,
|
|
141
110
|
label: this.label,
|
|
111
|
+
name: this.name,
|
|
112
|
+
operation: this.operation,
|
|
142
113
|
type: this.type,
|
|
143
|
-
value: this.value,
|
|
144
|
-
hidden: this.hidden,
|
|
145
|
-
options: this.options,
|
|
146
114
|
};
|
|
147
115
|
}
|
|
148
116
|
}
|
|
149
|
-
exports.
|
|
117
|
+
exports.IslandAction = IslandAction;
|
|
150
118
|
/**
|
|
151
|
-
* Island
|
|
119
|
+
* Island field definition for interactive UI components
|
|
152
120
|
*/
|
|
153
|
-
class
|
|
121
|
+
class IslandField {
|
|
154
122
|
/**
|
|
155
|
-
* Create a new island
|
|
156
|
-
* @param name Programmatic
|
|
157
|
-
* @param label Human-readable
|
|
158
|
-
* @param type
|
|
159
|
-
* @param
|
|
160
|
-
* @param
|
|
123
|
+
* Create a new island field
|
|
124
|
+
* @param name Programmatic field identifier
|
|
125
|
+
* @param label Human-readable label
|
|
126
|
+
* @param type Field type
|
|
127
|
+
* @param value Current field value
|
|
128
|
+
* @param hidden Whether to hide from user
|
|
129
|
+
* @param options Available options for selection
|
|
161
130
|
*/
|
|
162
|
-
constructor(name, label, type,
|
|
131
|
+
constructor(name, label, type, value = "", hidden = false, options = []) {
|
|
163
132
|
this.name = name;
|
|
164
133
|
this.label = label;
|
|
165
134
|
this.type = type;
|
|
166
|
-
this.
|
|
167
|
-
this.
|
|
135
|
+
this.value = value;
|
|
136
|
+
this.hidden = hidden;
|
|
137
|
+
this.options = options;
|
|
168
138
|
}
|
|
169
139
|
/**
|
|
170
140
|
* Convert to JSON for the discovery endpoint
|
|
171
141
|
*/
|
|
172
142
|
toJSON() {
|
|
173
143
|
return {
|
|
174
|
-
|
|
144
|
+
hidden: this.hidden,
|
|
175
145
|
label: this.label,
|
|
146
|
+
name: this.name,
|
|
147
|
+
options: this.options,
|
|
176
148
|
type: this.type,
|
|
177
|
-
|
|
178
|
-
operation: this.operation,
|
|
149
|
+
value: this.value,
|
|
179
150
|
};
|
|
180
151
|
}
|
|
181
152
|
}
|
|
182
|
-
exports.
|
|
153
|
+
exports.IslandField = IslandField;
|
|
183
154
|
/**
|
|
184
155
|
* Island configuration for interactive UI components
|
|
185
156
|
*/
|
|
@@ -188,24 +159,28 @@ class IslandConfig {
|
|
|
188
159
|
* Create a new island configuration
|
|
189
160
|
* @param fields List of island fields
|
|
190
161
|
* @param actions List of island actions
|
|
162
|
+
* @param type Optional island type
|
|
163
|
+
* @param icon Optional island icon
|
|
191
164
|
*/
|
|
192
|
-
constructor(fields, actions) {
|
|
165
|
+
constructor(fields, actions, type, icon) {
|
|
193
166
|
this.fields = fields;
|
|
194
167
|
this.actions = actions;
|
|
168
|
+
this.type = type;
|
|
169
|
+
this.icon = icon;
|
|
195
170
|
}
|
|
196
171
|
/**
|
|
197
172
|
* Convert to JSON for the discovery endpoint
|
|
198
173
|
*/
|
|
199
174
|
toJSON() {
|
|
200
175
|
return {
|
|
201
|
-
fields: this.fields.map((field) => field.toJSON()),
|
|
202
176
|
actions: this.actions.map((action) => action.toJSON()),
|
|
177
|
+
fields: this.fields.map((field) => field.toJSON()),
|
|
203
178
|
};
|
|
204
179
|
}
|
|
205
180
|
}
|
|
206
181
|
exports.IslandConfig = IslandConfig;
|
|
207
|
-
IslandConfig.Field = IslandField;
|
|
208
182
|
IslandConfig.Action = IslandAction;
|
|
183
|
+
IslandConfig.Field = IslandField;
|
|
209
184
|
/**
|
|
210
185
|
* Island response configuration
|
|
211
186
|
*/
|
|
@@ -234,18 +209,21 @@ class IslandResponse {
|
|
|
234
209
|
/**
|
|
235
210
|
* Create a new island response
|
|
236
211
|
* @param config Response configuration
|
|
212
|
+
* @param message Optional message for the island response
|
|
237
213
|
*/
|
|
238
|
-
constructor(config) {
|
|
214
|
+
constructor(config, message) {
|
|
239
215
|
this.config = config;
|
|
216
|
+
this.message = message;
|
|
240
217
|
this.type = "island";
|
|
241
218
|
}
|
|
242
219
|
/**
|
|
243
220
|
* Create an island response with a list of islands
|
|
244
221
|
* @param islands List of island configurations
|
|
222
|
+
* @param message Optional message for the island response
|
|
245
223
|
* @returns New IslandResponse instance
|
|
246
224
|
*/
|
|
247
|
-
static create(islands) {
|
|
248
|
-
return new IslandResponse(new IslandResponseConfig(islands));
|
|
225
|
+
static create(islands, message) {
|
|
226
|
+
return new IslandResponse(new IslandResponseConfig(islands), message);
|
|
249
227
|
}
|
|
250
228
|
/**
|
|
251
229
|
* Convert to JSON for the discovery endpoint
|
|
@@ -258,3 +236,33 @@ class IslandResponse {
|
|
|
258
236
|
}
|
|
259
237
|
exports.IslandResponse = IslandResponse;
|
|
260
238
|
IslandResponse.ResponseConfig = IslandResponseConfig;
|
|
239
|
+
/**
|
|
240
|
+
* Parameter definition for an Opal tool
|
|
241
|
+
*/
|
|
242
|
+
class Parameter {
|
|
243
|
+
/**
|
|
244
|
+
* Create a new parameter definition
|
|
245
|
+
* @param name Parameter name
|
|
246
|
+
* @param type Parameter type
|
|
247
|
+
* @param description Parameter description
|
|
248
|
+
* @param required Whether the parameter is required
|
|
249
|
+
*/
|
|
250
|
+
constructor(name, type, description, required) {
|
|
251
|
+
this.name = name;
|
|
252
|
+
this.type = type;
|
|
253
|
+
this.description = description;
|
|
254
|
+
this.required = required;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Convert to JSON for the discovery endpoint
|
|
258
|
+
*/
|
|
259
|
+
toJSON() {
|
|
260
|
+
return {
|
|
261
|
+
description: this.description,
|
|
262
|
+
name: this.name,
|
|
263
|
+
required: this.required,
|
|
264
|
+
type: this.type,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
exports.Parameter = Parameter;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
import type { BlockResponse } from "./block";
|
|
3
|
+
import { Credentials } from "./models";
|
|
4
|
+
/**
|
|
5
|
+
* Extra context passed to tool handlers
|
|
6
|
+
*/
|
|
7
|
+
export type RequestHandlerExtra = {
|
|
8
|
+
/** Authentication data if provided */
|
|
9
|
+
auth?: {
|
|
10
|
+
credentials: Credentials;
|
|
11
|
+
provider: string;
|
|
12
|
+
};
|
|
13
|
+
/** Execution mode: 'headless' for non-interactive, 'interactive' for user interaction (defaults to 'headless') */
|
|
14
|
+
mode: "headless" | "interactive";
|
|
15
|
+
};
|
|
16
|
+
type ToolOptions<TSchema extends Record<string, z.ZodTypeAny>, TType extends "block" | "json" = "json"> = {
|
|
17
|
+
authRequirements?: {
|
|
18
|
+
provider: string;
|
|
19
|
+
required?: boolean;
|
|
20
|
+
scopeBundle: string;
|
|
21
|
+
};
|
|
22
|
+
description: string;
|
|
23
|
+
inputSchema: TSchema;
|
|
24
|
+
type?: TType;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Register a function as an Opal tool
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const greetTool = registerTool('greet', {
|
|
32
|
+
* description: 'Greet a user',
|
|
33
|
+
* inputSchema: {
|
|
34
|
+
* name: z.string().describe('The name to greet')
|
|
35
|
+
* }
|
|
36
|
+
* }, async (params) => {
|
|
37
|
+
* // params is automatically typed as { name: string }
|
|
38
|
+
* return `Hello, ${params.name}!`;
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* // With auth and execution mode
|
|
45
|
+
* const fetchTool = registerTool('fetch_data', {
|
|
46
|
+
* description: 'Fetch data from API',
|
|
47
|
+
* inputSchema: {
|
|
48
|
+
* url: z.string().describe('URL to fetch')
|
|
49
|
+
* },
|
|
50
|
+
* authRequirements: {
|
|
51
|
+
* provider: 'api-service',
|
|
52
|
+
* scopeBundle: 'read'
|
|
53
|
+
* }
|
|
54
|
+
* }, async (params, extra) => {
|
|
55
|
+
* // extra.mode: 'headless' | 'interactive'
|
|
56
|
+
* // extra.auth: { provider, credentials }
|
|
57
|
+
* const headers = extra?.auth ? { Authorization: extra.auth.credentials.token } : {};
|
|
58
|
+
* return fetch(params.url, { headers });
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function registerTool<TSchema extends Record<string, z.ZodTypeAny>>(name: string, options: ToolOptions<TSchema, "block">, handler: (params: {
|
|
63
|
+
[K in keyof TSchema]: z.infer<TSchema[K]>;
|
|
64
|
+
}, extra?: RequestHandlerExtra) => BlockResponse | Promise<BlockResponse>): typeof handler;
|
|
65
|
+
export declare function registerTool<TSchema extends Record<string, z.ZodTypeAny>>(name: string, options: Omit<ToolOptions<TSchema>, "type"> | ToolOptions<TSchema, "json">, handler: (params: {
|
|
66
|
+
[K in keyof TSchema]: z.infer<TSchema[K]>;
|
|
67
|
+
}, extra?: RequestHandlerExtra) => Promise<unknown> | unknown): typeof handler;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerTool = registerTool;
|
|
4
|
+
const v4_1 = require("zod/v4");
|
|
5
|
+
const models_1 = require("./models");
|
|
6
|
+
const registry_1 = require("./registry");
|
|
7
|
+
// Implementation
|
|
8
|
+
function registerTool(name, options, handler) {
|
|
9
|
+
// Register the tool with all services
|
|
10
|
+
const responseType = options.type || "json";
|
|
11
|
+
for (const service of registry_1.registry.services) {
|
|
12
|
+
service.registerTool(name, options.description, handler, jsonSchemaToParameters(v4_1.z.toJSONSchema(v4_1.z.object(options.inputSchema))), `/tools/${name.replace(/_/g, "-")}`, options.authRequirements
|
|
13
|
+
? [
|
|
14
|
+
new models_1.AuthRequirement(options.authRequirements.provider, options.authRequirements.scopeBundle, options.authRequirements.required ?? true),
|
|
15
|
+
]
|
|
16
|
+
: undefined, responseType, true);
|
|
17
|
+
}
|
|
18
|
+
return handler;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Convert JSON Schema to Parameter definitions
|
|
22
|
+
* This converts the output of z.toJSONSchema() back to the Parameter[] format
|
|
23
|
+
* expected by the legacy discovery endpoint
|
|
24
|
+
*/
|
|
25
|
+
function jsonSchemaToParameters(jsonSchema) {
|
|
26
|
+
const parameters = [];
|
|
27
|
+
if (!jsonSchema.properties) {
|
|
28
|
+
return parameters;
|
|
29
|
+
}
|
|
30
|
+
const required = jsonSchema.required || [];
|
|
31
|
+
for (const [key, value] of Object.entries(jsonSchema.properties)) {
|
|
32
|
+
const prop = value;
|
|
33
|
+
parameters.push(new models_1.Parameter(key, jsonSchemaTypeToParameterType(prop.type || "string"), prop.description || "", required.includes(key)));
|
|
34
|
+
}
|
|
35
|
+
return parameters;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Map JSON Schema type to ParameterType
|
|
39
|
+
*/
|
|
40
|
+
function jsonSchemaTypeToParameterType(jsonType) {
|
|
41
|
+
switch (jsonType) {
|
|
42
|
+
case "array":
|
|
43
|
+
return models_1.ParameterType.List;
|
|
44
|
+
case "boolean":
|
|
45
|
+
return models_1.ParameterType.Boolean;
|
|
46
|
+
case "integer":
|
|
47
|
+
return models_1.ParameterType.Integer;
|
|
48
|
+
case "number":
|
|
49
|
+
return models_1.ParameterType.Number;
|
|
50
|
+
case "object":
|
|
51
|
+
return models_1.ParameterType.Dictionary;
|
|
52
|
+
case "string":
|
|
53
|
+
return models_1.ParameterType.String;
|
|
54
|
+
default:
|
|
55
|
+
return models_1.ParameterType.String;
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/registry.d.ts
CHANGED
package/dist/registry.js
CHANGED
package/dist/service.d.ts
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import { Express } from
|
|
2
|
-
import { AuthRequirement, Parameter } from
|
|
1
|
+
import { Express } from "express";
|
|
2
|
+
import { AuthRequirement, Parameter } from "./models";
|
|
3
3
|
export declare class ToolsService {
|
|
4
4
|
private app;
|
|
5
|
-
private router;
|
|
6
5
|
private functions;
|
|
6
|
+
private router;
|
|
7
7
|
/**
|
|
8
8
|
* Initialize a new tools service
|
|
9
9
|
* @param app Express application
|
|
10
10
|
*/
|
|
11
11
|
constructor(app: Express);
|
|
12
|
-
/**
|
|
13
|
-
* Initialize the discovery endpoint
|
|
14
|
-
*/
|
|
15
|
-
private initRoutes;
|
|
16
12
|
/**
|
|
17
13
|
* Register a tool function
|
|
18
14
|
* @param name Tool name
|
|
@@ -21,7 +17,13 @@ export declare class ToolsService {
|
|
|
21
17
|
* @param parameters List of parameters for the tool
|
|
22
18
|
* @param endpoint API endpoint for the tool
|
|
23
19
|
* @param authRequirements Authentication requirements (optional)
|
|
20
|
+
* @param responseType Response type - 'json' (default) or 'block'
|
|
21
|
+
* @param isNewStyle Whether this is a new-style tool (registerTool) vs legacy decorator
|
|
24
22
|
*/
|
|
25
23
|
registerTool(name: string, description: string, handler: any, // Changed from Function to any to avoid confusion with built-in Function type
|
|
26
|
-
parameters: Parameter[], endpoint: string, authRequirements?: AuthRequirement[]): void;
|
|
24
|
+
parameters: Parameter[], endpoint: string, authRequirements?: AuthRequirement[], responseType?: "block" | "json", isNewStyle?: boolean): void;
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the discovery endpoint
|
|
27
|
+
*/
|
|
28
|
+
private initRoutes;
|
|
27
29
|
}
|
package/dist/service.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ToolsService = void 0;
|
|
7
7
|
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const block_1 = require("./block");
|
|
8
9
|
const models_1 = require("./models");
|
|
9
10
|
const registry_1 = require("./registry");
|
|
10
11
|
class ToolsService {
|
|
@@ -20,15 +21,6 @@ class ToolsService {
|
|
|
20
21
|
// Register this service in the global registry
|
|
21
22
|
registry_1.registry.services.push(this);
|
|
22
23
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Initialize the discovery endpoint
|
|
25
|
-
*/
|
|
26
|
-
initRoutes() {
|
|
27
|
-
this.router.get('/discovery', (req, res) => {
|
|
28
|
-
res.json({ functions: this.functions.map(f => f.toJSON()) });
|
|
29
|
-
});
|
|
30
|
-
this.app.use(this.router);
|
|
31
|
-
}
|
|
32
24
|
/**
|
|
33
25
|
* Register a tool function
|
|
34
26
|
* @param name Tool name
|
|
@@ -37,11 +29,17 @@ class ToolsService {
|
|
|
37
29
|
* @param parameters List of parameters for the tool
|
|
38
30
|
* @param endpoint API endpoint for the tool
|
|
39
31
|
* @param authRequirements Authentication requirements (optional)
|
|
32
|
+
* @param responseType Response type - 'json' (default) or 'block'
|
|
33
|
+
* @param isNewStyle Whether this is a new-style tool (registerTool) vs legacy decorator
|
|
40
34
|
*/
|
|
41
|
-
registerTool(name, description,
|
|
42
|
-
|
|
35
|
+
registerTool(name, description,
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
handler, // Changed from Function to any to avoid confusion with built-in Function type
|
|
38
|
+
parameters, endpoint, authRequirements, responseType = "json", isNewStyle = false) {
|
|
43
39
|
const func = new models_1.Function(name, description, parameters, endpoint, authRequirements);
|
|
44
40
|
this.functions.push(func);
|
|
41
|
+
// Determine if this is a block tool
|
|
42
|
+
const isBlockTool = responseType === "block";
|
|
45
43
|
// Register the actual endpoint
|
|
46
44
|
this.router.post(endpoint, async (req, res) => {
|
|
47
45
|
try {
|
|
@@ -61,28 +59,58 @@ class ToolsService {
|
|
|
61
59
|
// Extract auth data if available
|
|
62
60
|
const authData = req.body && req.body.auth;
|
|
63
61
|
if (authData) {
|
|
64
|
-
console.log(`Auth data provided for provider: ${authData.provider ||
|
|
62
|
+
console.log(`Auth data provided for provider: ${authData.provider || "unknown"}`);
|
|
65
63
|
}
|
|
66
|
-
// Call the handler with extracted parameters
|
|
67
|
-
// Check if handler accepts auth as third parameter
|
|
68
|
-
const handlerParamCount = handler.length;
|
|
64
|
+
// Call the handler with extracted parameters
|
|
69
65
|
let result;
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
if (isNewStyle) {
|
|
67
|
+
result = await handler(params, {
|
|
68
|
+
mode: (req.body && req.body.execution_mode) || "headless",
|
|
69
|
+
...(authData && { auth: authData }),
|
|
70
|
+
});
|
|
73
71
|
}
|
|
74
72
|
else {
|
|
75
|
-
//
|
|
76
|
-
|
|
73
|
+
// Check if handler accepts auth as third parameter
|
|
74
|
+
const handlerParamCount = handler.length;
|
|
75
|
+
if (handlerParamCount >= 2) {
|
|
76
|
+
// Handler accepts auth data
|
|
77
|
+
result = await handler(params, authData);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Handler doesn't accept auth data
|
|
81
|
+
result = await handler(params);
|
|
82
|
+
}
|
|
77
83
|
}
|
|
78
84
|
console.log(`Tool ${name} returned:`, result);
|
|
79
|
-
|
|
85
|
+
// Return with appropriate content-type header
|
|
86
|
+
if (isBlockTool) {
|
|
87
|
+
// Validate that block tools return a BlockResponse
|
|
88
|
+
if (!(0, block_1.isBlockResponse)(result)) {
|
|
89
|
+
throw new Error(`Block tool '${name}' must return a BlockResponse object, but returned ${typeof result}`);
|
|
90
|
+
}
|
|
91
|
+
res.set("Content-Type", "application/vnd.opal.block+json");
|
|
92
|
+
res.json(result);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
res.json(result);
|
|
96
|
+
}
|
|
80
97
|
}
|
|
81
98
|
catch (error) {
|
|
82
99
|
console.error(`Error in tool ${name}:`, error);
|
|
83
|
-
res.status(500).json({
|
|
100
|
+
res.status(500).json({
|
|
101
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
102
|
+
});
|
|
84
103
|
}
|
|
85
104
|
});
|
|
86
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Initialize the discovery endpoint
|
|
108
|
+
*/
|
|
109
|
+
initRoutes() {
|
|
110
|
+
this.router.get("/discovery", (req, res) => {
|
|
111
|
+
res.json({ functions: this.functions.map((f) => f.toJSON()) });
|
|
112
|
+
});
|
|
113
|
+
this.app.use(this.router);
|
|
114
|
+
}
|
|
87
115
|
}
|
|
88
116
|
exports.ToolsService = ToolsService;
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const typescript = require("typescript-eslint");
|
|
2
|
+
const perfectionist = require("eslint-plugin-perfectionist");
|
|
3
|
+
|
|
4
|
+
module.exports = typescript.config(
|
|
5
|
+
{
|
|
6
|
+
ignores: ["dist/**", "node_modules/**", "*.config.js"],
|
|
7
|
+
},
|
|
8
|
+
...typescript.configs.recommended,
|
|
9
|
+
perfectionist.configs["recommended-natural"],
|
|
10
|
+
{
|
|
11
|
+
files: ["**/*.ts"],
|
|
12
|
+
ignores: ["*.config.ts"],
|
|
13
|
+
languageOptions: {
|
|
14
|
+
parserOptions: {
|
|
15
|
+
projectService: true,
|
|
16
|
+
tsconfigRootDir: __dirname,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optimizely-opal/opal-tools-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6-dev",
|
|
4
4
|
"description": "SDK for creating Opal-compatible tools services",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
8
|
+
"build": "tsc -p tsconfig.build.json",
|
|
9
|
+
"generate:block": "ts-node scripts/generate-block.ts",
|
|
10
|
+
"lint": "bash scripts/lint.sh",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"test": "vitest run"
|
|
11
13
|
},
|
|
12
14
|
"keywords": [
|
|
13
15
|
"opal",
|
|
@@ -19,20 +21,27 @@
|
|
|
19
21
|
"author": "Optimizely",
|
|
20
22
|
"license": "MIT",
|
|
21
23
|
"dependencies": {
|
|
22
|
-
"express": "^4.18.2",
|
|
23
|
-
"axios": "^1.6.0",
|
|
24
24
|
"reflect-metadata": "^0.1.13"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/express": "^4.17.17",
|
|
28
|
-
"@types/jest": "^29.5.3",
|
|
29
27
|
"@types/node": "^20.4.5",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
28
|
+
"@types/supertest": "^6.0.2",
|
|
29
|
+
"eslint": "^9.39.2",
|
|
30
|
+
"eslint-plugin-perfectionist": "^5.4.0",
|
|
31
|
+
"json-schema-to-typescript": "^13.1.1",
|
|
32
|
+
"prettier": "^3.8.1",
|
|
33
|
+
"supertest": "^7.0.0",
|
|
34
|
+
"ts-node": "^10.9.2",
|
|
35
|
+
"typescript-eslint": "^8.54.0",
|
|
36
|
+
"vitest": "^1.2.0"
|
|
33
37
|
},
|
|
34
38
|
"peerDependencies": {
|
|
35
|
-
"express": "^4.
|
|
39
|
+
"@types/express": "^4.17.17",
|
|
40
|
+
"@types/node": "^20.4.5",
|
|
41
|
+
"axios": "^1.6.0",
|
|
42
|
+
"express": "^4.18.2",
|
|
43
|
+
"typescript": "^5.1.6",
|
|
44
|
+
"zod": "^3.25.0 || ^4.0.0"
|
|
36
45
|
},
|
|
37
46
|
"repository": {
|
|
38
47
|
"type": "git",
|