@optimizely-opal/opal-tools-sdk 0.1.5-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 -202
- 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 +115 -115
- package/dist/models.js +77 -76
- 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 +20 -10
- 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 +106 -103
- 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/src/decorators.ts
CHANGED
|
@@ -1,70 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import "reflect-metadata";
|
|
3
|
+
|
|
4
|
+
import { AuthRequirement, Parameter, ParameterType } from "./models";
|
|
5
|
+
import { registry } from "./registry";
|
|
4
6
|
|
|
5
7
|
interface ParameterDefinition {
|
|
6
|
-
name: string;
|
|
7
|
-
type: ParameterType;
|
|
8
8
|
description: string;
|
|
9
|
+
name: string;
|
|
9
10
|
required: boolean;
|
|
11
|
+
type: ParameterType;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
interface ToolOptions {
|
|
13
|
-
name: string;
|
|
14
|
-
description: string;
|
|
15
|
-
parameters?: ParameterDefinition[];
|
|
16
15
|
authRequirements?: {
|
|
17
16
|
provider: string;
|
|
18
|
-
scopeBundle: string;
|
|
19
17
|
required?: boolean;
|
|
18
|
+
scopeBundle: string;
|
|
20
19
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* Map a TypeScript type to a ParameterType
|
|
25
|
-
* @param type TypeScript type
|
|
26
|
-
*/
|
|
27
|
-
function mapTypeToParameterType(type: any): ParameterType {
|
|
28
|
-
if (type === String || type.name === 'String') {
|
|
29
|
-
return ParameterType.String;
|
|
30
|
-
} else if (type === Number || type.name === 'Number') {
|
|
31
|
-
return ParameterType.Number;
|
|
32
|
-
} else if (type === Boolean || type.name === 'Boolean') {
|
|
33
|
-
return ParameterType.Boolean;
|
|
34
|
-
} else if (type === Array || type.name === 'Array') {
|
|
35
|
-
return ParameterType.List;
|
|
36
|
-
} else if (type === Object || type.name === 'Object') {
|
|
37
|
-
return ParameterType.Dictionary;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Default to string
|
|
41
|
-
return ParameterType.String;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Extract parameters from a TypeScript interface
|
|
46
|
-
* @param paramType Parameter type object
|
|
47
|
-
*/
|
|
48
|
-
function extractParameters(paramType: any): Parameter[] {
|
|
49
|
-
const parameters: Parameter[] = [];
|
|
50
|
-
|
|
51
|
-
// This is very basic and doesn't handle complex types
|
|
52
|
-
// For production use, this would need to be more sophisticated
|
|
53
|
-
for (const key in paramType) {
|
|
54
|
-
if (paramType.hasOwnProperty(key)) {
|
|
55
|
-
const type = typeof paramType[key] === 'undefined' ? String : paramType[key].constructor;
|
|
56
|
-
const required = true; // In a real implementation, we'd detect optional parameters
|
|
57
|
-
|
|
58
|
-
parameters.push(new Parameter(
|
|
59
|
-
key,
|
|
60
|
-
mapTypeToParameterType(type),
|
|
61
|
-
'', // Description - in a real impl we'd use TypeDoc or similar
|
|
62
|
-
required
|
|
63
|
-
));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return parameters;
|
|
20
|
+
description: string;
|
|
21
|
+
name: string;
|
|
22
|
+
parameters?: ParameterDefinition[];
|
|
68
23
|
}
|
|
69
24
|
|
|
70
25
|
/**
|
|
@@ -84,24 +39,30 @@ function extractParameters(paramType: any): Parameter[] {
|
|
|
84
39
|
* ```
|
|
85
40
|
*/
|
|
86
41
|
export function tool(options: ToolOptions) {
|
|
87
|
-
return function(
|
|
42
|
+
return function (
|
|
43
|
+
target: any,
|
|
44
|
+
propertyKey?: string,
|
|
45
|
+
descriptor?: PropertyDescriptor,
|
|
46
|
+
) {
|
|
88
47
|
const isMethod = propertyKey && descriptor;
|
|
89
48
|
const handler = isMethod ? descriptor.value : target;
|
|
90
49
|
|
|
91
50
|
// Generate endpoint from name - ensure hyphens instead of underscores
|
|
92
|
-
const endpoint = `/tools/${options.name.replace(/_/g,
|
|
51
|
+
const endpoint = `/tools/${options.name.replace(/_/g, "-")}`;
|
|
93
52
|
|
|
94
53
|
// Convert parameter definitions to Parameter objects
|
|
95
54
|
const parameters: Parameter[] = [];
|
|
96
55
|
if (options.parameters && options.parameters.length > 0) {
|
|
97
56
|
// Use the explicitly provided parameter definitions
|
|
98
57
|
for (const paramDef of options.parameters) {
|
|
99
|
-
parameters.push(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
58
|
+
parameters.push(
|
|
59
|
+
new Parameter(
|
|
60
|
+
paramDef.name,
|
|
61
|
+
paramDef.type,
|
|
62
|
+
paramDef.description,
|
|
63
|
+
paramDef.required,
|
|
64
|
+
),
|
|
65
|
+
);
|
|
105
66
|
}
|
|
106
67
|
}
|
|
107
68
|
|
|
@@ -112,8 +73,8 @@ export function tool(options: ToolOptions) {
|
|
|
112
73
|
new AuthRequirement(
|
|
113
74
|
options.authRequirements.provider,
|
|
114
75
|
options.authRequirements.scopeBundle,
|
|
115
|
-
options.authRequirements.required ?? true
|
|
116
|
-
)
|
|
76
|
+
options.authRequirements.required ?? true,
|
|
77
|
+
),
|
|
117
78
|
];
|
|
118
79
|
}
|
|
119
80
|
|
|
@@ -125,7 +86,7 @@ export function tool(options: ToolOptions) {
|
|
|
125
86
|
handler,
|
|
126
87
|
parameters,
|
|
127
88
|
endpoint,
|
|
128
|
-
authRequirements
|
|
89
|
+
authRequirements,
|
|
129
90
|
);
|
|
130
91
|
}
|
|
131
92
|
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "reflect-metadata";
|
|
2
2
|
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export
|
|
6
|
-
export
|
|
3
|
+
export { requiresAuth } from "./auth";
|
|
4
|
+
export { Block, isBlockResponse } from "./block";
|
|
5
|
+
export type * from "./block";
|
|
6
|
+
export { tool } from "./decorators";
|
|
7
|
+
export * from "./models";
|
|
8
|
+
export { registerTool } from "./registerTool";
|
|
9
|
+
export type { RequestHandlerExtra } from "./registerTool";
|
|
10
|
+
export { ToolsService } from "./service";
|
package/src/models.ts
CHANGED
|
@@ -2,44 +2,39 @@
|
|
|
2
2
|
* Types of parameters supported by Opal tools
|
|
3
3
|
*/
|
|
4
4
|
export enum ParameterType {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Boolean = "boolean",
|
|
6
|
+
Dictionary = "object",
|
|
7
|
+
Integer = "integer",
|
|
8
|
+
List = "array",
|
|
9
|
+
Number = "number",
|
|
10
|
+
String = "string",
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Authentication data for an Opal tool
|
|
15
15
|
*/
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* @param type Parameter type
|
|
21
|
-
* @param description Parameter description
|
|
22
|
-
* @param required Whether the parameter is required
|
|
23
|
-
*/
|
|
24
|
-
constructor(
|
|
25
|
-
public name: string,
|
|
26
|
-
public type: ParameterType,
|
|
27
|
-
public description: string,
|
|
28
|
-
public required: boolean
|
|
29
|
-
) {}
|
|
16
|
+
export type AuthData = {
|
|
17
|
+
credentials: Credentials;
|
|
18
|
+
provider: string;
|
|
19
|
+
};
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Authentication credentials structure
|
|
23
|
+
*/
|
|
24
|
+
export type Credentials = {
|
|
25
|
+
access_token: string;
|
|
26
|
+
customer_id: string;
|
|
27
|
+
instance_id: string;
|
|
28
|
+
org_sso_id?: string;
|
|
29
|
+
product_sku: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Execution environment for an Opal tool
|
|
34
|
+
*/
|
|
35
|
+
export type Environment = {
|
|
36
|
+
execution_mode: "headless" | "interactive";
|
|
37
|
+
};
|
|
43
38
|
|
|
44
39
|
/**
|
|
45
40
|
* Authentication requirements for an Opal tool
|
|
@@ -54,7 +49,7 @@ export class AuthRequirement {
|
|
|
54
49
|
constructor(
|
|
55
50
|
public provider: string,
|
|
56
51
|
public scopeBundle: string,
|
|
57
|
-
public required: boolean = true
|
|
52
|
+
public required: boolean = true,
|
|
58
53
|
) {}
|
|
59
54
|
|
|
60
55
|
/**
|
|
@@ -63,8 +58,8 @@ export class AuthRequirement {
|
|
|
63
58
|
toJSON() {
|
|
64
59
|
return {
|
|
65
60
|
provider: this.provider,
|
|
61
|
+
required: this.required,
|
|
66
62
|
scope_bundle: this.scopeBundle,
|
|
67
|
-
required: this.required
|
|
68
63
|
};
|
|
69
64
|
}
|
|
70
65
|
}
|
|
@@ -76,7 +71,7 @@ export class Function {
|
|
|
76
71
|
/**
|
|
77
72
|
* HTTP method for the endpoint (default: POST)
|
|
78
73
|
*/
|
|
79
|
-
public httpMethod: string =
|
|
74
|
+
public httpMethod: string = "POST";
|
|
80
75
|
|
|
81
76
|
/**
|
|
82
77
|
* Create a new function definition
|
|
@@ -91,23 +86,26 @@ export class Function {
|
|
|
91
86
|
public description: string,
|
|
92
87
|
public parameters: Parameter[],
|
|
93
88
|
public endpoint: string,
|
|
94
|
-
public authRequirements?: AuthRequirement[]
|
|
89
|
+
public authRequirements?: AuthRequirement[],
|
|
95
90
|
) {}
|
|
96
91
|
|
|
97
92
|
/**
|
|
98
93
|
* Convert to JSON for the discovery endpoint
|
|
99
94
|
*/
|
|
100
95
|
toJSON() {
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
97
|
const result: any = {
|
|
102
|
-
name: this.name,
|
|
103
98
|
description: this.description,
|
|
104
|
-
parameters: this.parameters.map(p => p.toJSON()),
|
|
105
99
|
endpoint: this.endpoint,
|
|
106
|
-
http_method: this.httpMethod
|
|
100
|
+
http_method: this.httpMethod,
|
|
101
|
+
name: this.name,
|
|
102
|
+
parameters: this.parameters.map((p) => p.toJSON()),
|
|
107
103
|
};
|
|
108
104
|
|
|
109
105
|
if (this.authRequirements && this.authRequirements.length > 0) {
|
|
110
|
-
result.auth_requirements = this.authRequirements.map(auth =>
|
|
106
|
+
result.auth_requirements = this.authRequirements.map((auth) =>
|
|
107
|
+
auth.toJSON(),
|
|
108
|
+
);
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
return result;
|
|
@@ -115,51 +113,23 @@ export class Function {
|
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
/**
|
|
118
|
-
*
|
|
119
|
-
*/
|
|
120
|
-
export type Credentials ={
|
|
121
|
-
access_token: string;
|
|
122
|
-
org_sso_id?: string;
|
|
123
|
-
customer_id: string;
|
|
124
|
-
instance_id: string;
|
|
125
|
-
product_sku: string;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Authentication data for an Opal tool
|
|
130
|
-
*/
|
|
131
|
-
export type AuthData = {
|
|
132
|
-
provider: string;
|
|
133
|
-
credentials: Credentials;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Execution environment for an Opal tool
|
|
138
|
-
*/
|
|
139
|
-
export type Environment = {
|
|
140
|
-
execution_mode: "headless" | "interactive";
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Island field definition for interactive UI components
|
|
116
|
+
* Island action definition for interactive UI components
|
|
145
117
|
*/
|
|
146
|
-
export class
|
|
118
|
+
export class IslandAction {
|
|
147
119
|
/**
|
|
148
|
-
* Create a new island
|
|
149
|
-
* @param name Programmatic
|
|
150
|
-
* @param label Human-readable label
|
|
151
|
-
* @param type
|
|
152
|
-
* @param
|
|
153
|
-
* @param
|
|
154
|
-
* @param options Available options for selection
|
|
120
|
+
* Create a new island action
|
|
121
|
+
* @param name Programmatic action identifier
|
|
122
|
+
* @param label Human-readable button label
|
|
123
|
+
* @param type UI element type
|
|
124
|
+
* @param endpoint API endpoint to call
|
|
125
|
+
* @param operation Operation type
|
|
155
126
|
*/
|
|
156
127
|
constructor(
|
|
157
128
|
public name: string,
|
|
158
129
|
public label: string,
|
|
159
|
-
public type:
|
|
160
|
-
public
|
|
161
|
-
public
|
|
162
|
-
public options: string[] = []
|
|
130
|
+
public type: string,
|
|
131
|
+
public endpoint: string,
|
|
132
|
+
public operation: string = "create",
|
|
163
133
|
) {}
|
|
164
134
|
|
|
165
135
|
/**
|
|
@@ -167,34 +137,35 @@ export class IslandField {
|
|
|
167
137
|
*/
|
|
168
138
|
toJSON() {
|
|
169
139
|
return {
|
|
170
|
-
|
|
140
|
+
endpoint: this.endpoint,
|
|
171
141
|
label: this.label,
|
|
142
|
+
name: this.name,
|
|
143
|
+
operation: this.operation,
|
|
172
144
|
type: this.type,
|
|
173
|
-
value: this.value,
|
|
174
|
-
hidden: this.hidden,
|
|
175
|
-
options: this.options,
|
|
176
145
|
};
|
|
177
146
|
}
|
|
178
147
|
}
|
|
179
148
|
|
|
180
149
|
/**
|
|
181
|
-
* Island
|
|
150
|
+
* Island field definition for interactive UI components
|
|
182
151
|
*/
|
|
183
|
-
export class
|
|
152
|
+
export class IslandField {
|
|
184
153
|
/**
|
|
185
|
-
* Create a new island
|
|
186
|
-
* @param name Programmatic
|
|
187
|
-
* @param label Human-readable
|
|
188
|
-
* @param type
|
|
189
|
-
* @param
|
|
190
|
-
* @param
|
|
154
|
+
* Create a new island field
|
|
155
|
+
* @param name Programmatic field identifier
|
|
156
|
+
* @param label Human-readable label
|
|
157
|
+
* @param type Field type
|
|
158
|
+
* @param value Current field value
|
|
159
|
+
* @param hidden Whether to hide from user
|
|
160
|
+
* @param options Available options for selection
|
|
191
161
|
*/
|
|
192
162
|
constructor(
|
|
193
163
|
public name: string,
|
|
194
164
|
public label: string,
|
|
195
|
-
public type: string,
|
|
196
|
-
public
|
|
197
|
-
public
|
|
165
|
+
public type: "boolean" | "json" | "string",
|
|
166
|
+
public value: string = "",
|
|
167
|
+
public hidden: boolean = false,
|
|
168
|
+
public options: string[] = [],
|
|
198
169
|
) {}
|
|
199
170
|
|
|
200
171
|
/**
|
|
@@ -202,11 +173,12 @@ export class IslandAction {
|
|
|
202
173
|
*/
|
|
203
174
|
toJSON() {
|
|
204
175
|
return {
|
|
205
|
-
|
|
176
|
+
hidden: this.hidden,
|
|
206
177
|
label: this.label,
|
|
178
|
+
name: this.name,
|
|
179
|
+
options: this.options,
|
|
207
180
|
type: this.type,
|
|
208
|
-
|
|
209
|
-
operation: this.operation,
|
|
181
|
+
value: this.value,
|
|
210
182
|
};
|
|
211
183
|
}
|
|
212
184
|
}
|
|
@@ -215,8 +187,8 @@ export class IslandAction {
|
|
|
215
187
|
* Island configuration for interactive UI components
|
|
216
188
|
*/
|
|
217
189
|
export class IslandConfig {
|
|
218
|
-
static Field = IslandField;
|
|
219
190
|
static Action = IslandAction;
|
|
191
|
+
static Field = IslandField;
|
|
220
192
|
|
|
221
193
|
/**
|
|
222
194
|
* Create a new island configuration
|
|
@@ -229,7 +201,7 @@ export class IslandConfig {
|
|
|
229
201
|
public fields: IslandField[],
|
|
230
202
|
public actions: IslandAction[],
|
|
231
203
|
public type?: string,
|
|
232
|
-
public icon?: string
|
|
204
|
+
public icon?: string,
|
|
233
205
|
) {}
|
|
234
206
|
|
|
235
207
|
/**
|
|
@@ -237,8 +209,8 @@ export class IslandConfig {
|
|
|
237
209
|
*/
|
|
238
210
|
toJSON() {
|
|
239
211
|
return {
|
|
240
|
-
fields: this.fields.map((field) => field.toJSON()),
|
|
241
212
|
actions: this.actions.map((action) => action.toJSON()),
|
|
213
|
+
fields: this.fields.map((field) => field.toJSON()),
|
|
242
214
|
};
|
|
243
215
|
}
|
|
244
216
|
}
|
|
@@ -269,7 +241,7 @@ export class IslandResponseConfig {
|
|
|
269
241
|
export class IslandResponse {
|
|
270
242
|
static ResponseConfig = IslandResponseConfig;
|
|
271
243
|
|
|
272
|
-
public type
|
|
244
|
+
public type = "island" as const;
|
|
273
245
|
|
|
274
246
|
/**
|
|
275
247
|
* Create a new island response
|
|
@@ -278,7 +250,7 @@ export class IslandResponse {
|
|
|
278
250
|
*/
|
|
279
251
|
constructor(
|
|
280
252
|
public config: IslandResponseConfig,
|
|
281
|
-
public message?: string
|
|
253
|
+
public message?: string,
|
|
282
254
|
) {}
|
|
283
255
|
|
|
284
256
|
/**
|
|
@@ -300,3 +272,34 @@ export class IslandResponse {
|
|
|
300
272
|
};
|
|
301
273
|
}
|
|
302
274
|
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Parameter definition for an Opal tool
|
|
278
|
+
*/
|
|
279
|
+
export class Parameter {
|
|
280
|
+
/**
|
|
281
|
+
* Create a new parameter definition
|
|
282
|
+
* @param name Parameter name
|
|
283
|
+
* @param type Parameter type
|
|
284
|
+
* @param description Parameter description
|
|
285
|
+
* @param required Whether the parameter is required
|
|
286
|
+
*/
|
|
287
|
+
constructor(
|
|
288
|
+
public name: string,
|
|
289
|
+
public type: ParameterType,
|
|
290
|
+
public description: string,
|
|
291
|
+
public required: boolean,
|
|
292
|
+
) {}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Convert to JSON for the discovery endpoint
|
|
296
|
+
*/
|
|
297
|
+
toJSON() {
|
|
298
|
+
return {
|
|
299
|
+
description: this.description,
|
|
300
|
+
name: this.name,
|
|
301
|
+
required: this.required,
|
|
302
|
+
type: this.type,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { z } from "zod/v4";
|
|
2
|
+
|
|
3
|
+
import type { BlockResponse } from "./block";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AuthRequirement,
|
|
7
|
+
Credentials,
|
|
8
|
+
Parameter,
|
|
9
|
+
ParameterType,
|
|
10
|
+
} from "./models";
|
|
11
|
+
import { registry } from "./registry";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extra context passed to tool handlers
|
|
15
|
+
*/
|
|
16
|
+
export type RequestHandlerExtra = {
|
|
17
|
+
/** Authentication data if provided */
|
|
18
|
+
auth?: {
|
|
19
|
+
credentials: Credentials;
|
|
20
|
+
provider: string;
|
|
21
|
+
};
|
|
22
|
+
/** Execution mode: 'headless' for non-interactive, 'interactive' for user interaction (defaults to 'headless') */
|
|
23
|
+
mode: "headless" | "interactive";
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ToolOptions<
|
|
27
|
+
TSchema extends Record<string, z.ZodTypeAny>,
|
|
28
|
+
TType extends "block" | "json" = "json",
|
|
29
|
+
> = {
|
|
30
|
+
authRequirements?: {
|
|
31
|
+
provider: string;
|
|
32
|
+
required?: boolean;
|
|
33
|
+
scopeBundle: string;
|
|
34
|
+
};
|
|
35
|
+
description: string;
|
|
36
|
+
inputSchema: TSchema;
|
|
37
|
+
type?: TType;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Register a function as an Opal tool
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const greetTool = registerTool('greet', {
|
|
46
|
+
* description: 'Greet a user',
|
|
47
|
+
* inputSchema: {
|
|
48
|
+
* name: z.string().describe('The name to greet')
|
|
49
|
+
* }
|
|
50
|
+
* }, async (params) => {
|
|
51
|
+
* // params is automatically typed as { name: string }
|
|
52
|
+
* return `Hello, ${params.name}!`;
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // With auth and execution mode
|
|
59
|
+
* const fetchTool = registerTool('fetch_data', {
|
|
60
|
+
* description: 'Fetch data from API',
|
|
61
|
+
* inputSchema: {
|
|
62
|
+
* url: z.string().describe('URL to fetch')
|
|
63
|
+
* },
|
|
64
|
+
* authRequirements: {
|
|
65
|
+
* provider: 'api-service',
|
|
66
|
+
* scopeBundle: 'read'
|
|
67
|
+
* }
|
|
68
|
+
* }, async (params, extra) => {
|
|
69
|
+
* // extra.mode: 'headless' | 'interactive'
|
|
70
|
+
* // extra.auth: { provider, credentials }
|
|
71
|
+
* const headers = extra?.auth ? { Authorization: extra.auth.credentials.token } : {};
|
|
72
|
+
* return fetch(params.url, { headers });
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
// Overload for block tools
|
|
77
|
+
export function registerTool<TSchema extends Record<string, z.ZodTypeAny>>(
|
|
78
|
+
name: string,
|
|
79
|
+
options: ToolOptions<TSchema, "block">,
|
|
80
|
+
handler: (
|
|
81
|
+
params: { [K in keyof TSchema]: z.infer<TSchema[K]> },
|
|
82
|
+
extra?: RequestHandlerExtra,
|
|
83
|
+
) => BlockResponse | Promise<BlockResponse>,
|
|
84
|
+
): typeof handler;
|
|
85
|
+
// Overload for JSON tools (or when type is omitted)
|
|
86
|
+
export function registerTool<TSchema extends Record<string, z.ZodTypeAny>>(
|
|
87
|
+
name: string,
|
|
88
|
+
options: Omit<ToolOptions<TSchema>, "type"> | ToolOptions<TSchema, "json">,
|
|
89
|
+
handler: (
|
|
90
|
+
params: { [K in keyof TSchema]: z.infer<TSchema[K]> },
|
|
91
|
+
extra?: RequestHandlerExtra,
|
|
92
|
+
) => Promise<unknown> | unknown,
|
|
93
|
+
): typeof handler;
|
|
94
|
+
// Implementation
|
|
95
|
+
export function registerTool<TSchema extends Record<string, z.ZodTypeAny>>(
|
|
96
|
+
name: string,
|
|
97
|
+
options: ToolOptions<TSchema, "block" | "json">,
|
|
98
|
+
handler: (
|
|
99
|
+
params: { [K in keyof TSchema]: z.infer<TSchema[K]> },
|
|
100
|
+
extra?: RequestHandlerExtra,
|
|
101
|
+
) => Promise<unknown> | unknown,
|
|
102
|
+
): typeof handler {
|
|
103
|
+
// Register the tool with all services
|
|
104
|
+
const responseType = options.type || "json";
|
|
105
|
+
for (const service of registry.services) {
|
|
106
|
+
service.registerTool(
|
|
107
|
+
name,
|
|
108
|
+
options.description,
|
|
109
|
+
handler,
|
|
110
|
+
jsonSchemaToParameters(z.toJSONSchema(z.object(options.inputSchema))),
|
|
111
|
+
`/tools/${name.replace(/_/g, "-")}`,
|
|
112
|
+
options.authRequirements
|
|
113
|
+
? [
|
|
114
|
+
new AuthRequirement(
|
|
115
|
+
options.authRequirements.provider,
|
|
116
|
+
options.authRequirements.scopeBundle,
|
|
117
|
+
options.authRequirements.required ?? true,
|
|
118
|
+
),
|
|
119
|
+
]
|
|
120
|
+
: undefined,
|
|
121
|
+
responseType,
|
|
122
|
+
true,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return handler;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Convert JSON Schema to Parameter definitions
|
|
131
|
+
* This converts the output of z.toJSONSchema() back to the Parameter[] format
|
|
132
|
+
* expected by the legacy discovery endpoint
|
|
133
|
+
*/
|
|
134
|
+
function jsonSchemaToParameters(jsonSchema: {
|
|
135
|
+
properties?: Record<string, unknown>;
|
|
136
|
+
required?: string[];
|
|
137
|
+
}): Parameter[] {
|
|
138
|
+
const parameters: Parameter[] = [];
|
|
139
|
+
|
|
140
|
+
if (!jsonSchema.properties) {
|
|
141
|
+
return parameters;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const required = jsonSchema.required || [];
|
|
145
|
+
|
|
146
|
+
for (const [key, value] of Object.entries(jsonSchema.properties)) {
|
|
147
|
+
const prop = value as { description?: string; type?: string };
|
|
148
|
+
parameters.push(
|
|
149
|
+
new Parameter(
|
|
150
|
+
key,
|
|
151
|
+
jsonSchemaTypeToParameterType(prop.type || "string"),
|
|
152
|
+
prop.description || "",
|
|
153
|
+
required.includes(key),
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return parameters;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Map JSON Schema type to ParameterType
|
|
163
|
+
*/
|
|
164
|
+
function jsonSchemaTypeToParameterType(jsonType: string): ParameterType {
|
|
165
|
+
switch (jsonType) {
|
|
166
|
+
case "array":
|
|
167
|
+
return ParameterType.List;
|
|
168
|
+
case "boolean":
|
|
169
|
+
return ParameterType.Boolean;
|
|
170
|
+
case "integer":
|
|
171
|
+
return ParameterType.Integer;
|
|
172
|
+
case "number":
|
|
173
|
+
return ParameterType.Number;
|
|
174
|
+
case "object":
|
|
175
|
+
return ParameterType.Dictionary;
|
|
176
|
+
case "string":
|
|
177
|
+
return ParameterType.String;
|
|
178
|
+
default:
|
|
179
|
+
return ParameterType.String;
|
|
180
|
+
}
|
|
181
|
+
}
|
package/src/registry.ts
CHANGED