@optimizely-opal/opal-tools-sdk 0.1.0-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/README.md +48 -0
- package/dist/auth.d.ts +18 -0
- package/dist/auth.js +43 -0
- package/dist/decorators.d.ts +36 -0
- package/dist/decorators.js +91 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +25 -0
- package/dist/models.d.ts +87 -0
- package/dist/models.js +113 -0
- package/dist/registry.d.ts +7 -0
- package/dist/registry.js +9 -0
- package/dist/service.d.ts +27 -0
- package/dist/service.js +88 -0
- package/package.json +45 -0
- package/src/auth.ts +52 -0
- package/src/decorators.ts +134 -0
- package/src/index.ts +6 -0
- package/src/models.ts +115 -0
- package/src/registry.ts +8 -0
- package/src/service.ts +98 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Opal Tools SDK for TypeScript
|
|
2
|
+
|
|
3
|
+
This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Type definitions for tools and parameters
|
|
8
|
+
- Express middleware for discovery endpoints
|
|
9
|
+
- Decorators for tool registration
|
|
10
|
+
- Parameter validation
|
|
11
|
+
- Authentication helpers
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @optimizely-opal/opal-tools-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { ToolsService, tool } from '@optimizely-opal/opal-tools-sdk';
|
|
23
|
+
import express from 'express';
|
|
24
|
+
|
|
25
|
+
interface WeatherParameters {
|
|
26
|
+
location: string;
|
|
27
|
+
units?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const app = express();
|
|
31
|
+
const toolsService = new ToolsService(app);
|
|
32
|
+
|
|
33
|
+
@tool({
|
|
34
|
+
name: 'get_weather',
|
|
35
|
+
description: 'Gets current weather for a location'
|
|
36
|
+
})
|
|
37
|
+
async function getWeather(parameters: WeatherParameters) {
|
|
38
|
+
// Implementation...
|
|
39
|
+
return { temperature: 22, condition: 'sunny' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Discovery endpoint is automatically created at /discovery
|
|
43
|
+
app.listen(3000);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
47
|
+
|
|
48
|
+
See full documentation for more examples and configuration options.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware to handle authentication requirements
|
|
3
|
+
* @param req Express request
|
|
4
|
+
* @param res Express response
|
|
5
|
+
* @param next Express next function
|
|
6
|
+
*/
|
|
7
|
+
export declare function authMiddleware(req: any, res: any, next: any): any;
|
|
8
|
+
interface AuthOptions {
|
|
9
|
+
provider: string;
|
|
10
|
+
scopeBundle: string;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Decorator to indicate that a tool requires authentication
|
|
15
|
+
* @param options Authentication options
|
|
16
|
+
*/
|
|
17
|
+
export declare function requiresAuth(options: AuthOptions): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
18
|
+
export {};
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.authMiddleware = authMiddleware;
|
|
4
|
+
exports.requiresAuth = requiresAuth;
|
|
5
|
+
/**
|
|
6
|
+
* Middleware to handle authentication requirements
|
|
7
|
+
* @param req Express request
|
|
8
|
+
* @param res Express response
|
|
9
|
+
* @param next Express next function
|
|
10
|
+
*/
|
|
11
|
+
function authMiddleware(req, res, next) {
|
|
12
|
+
const authHeader = req.headers.authorization;
|
|
13
|
+
if (!authHeader && req.authRequired) {
|
|
14
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
15
|
+
}
|
|
16
|
+
// The Tools Management Service will provide the appropriate token
|
|
17
|
+
// in the Authorization header
|
|
18
|
+
next();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Decorator to indicate that a tool requires authentication
|
|
22
|
+
* @param options Authentication options
|
|
23
|
+
*/
|
|
24
|
+
function requiresAuth(options) {
|
|
25
|
+
return function (target, propertyKey, descriptor) {
|
|
26
|
+
const isMethod = propertyKey && descriptor;
|
|
27
|
+
if (isMethod && descriptor) {
|
|
28
|
+
const originalMethod = descriptor.value;
|
|
29
|
+
descriptor.value = function (...args) {
|
|
30
|
+
// Store auth requirements in function metadata
|
|
31
|
+
const fn = originalMethod;
|
|
32
|
+
fn.__authRequirements__ = {
|
|
33
|
+
provider: options.provider,
|
|
34
|
+
scopeBundle: options.scopeBundle,
|
|
35
|
+
required: options.required ?? true
|
|
36
|
+
};
|
|
37
|
+
return originalMethod.apply(this, args);
|
|
38
|
+
};
|
|
39
|
+
return descriptor;
|
|
40
|
+
}
|
|
41
|
+
return target;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { ParameterType } from './models';
|
|
3
|
+
interface ParameterDefinition {
|
|
4
|
+
name: string;
|
|
5
|
+
type: ParameterType;
|
|
6
|
+
description: string;
|
|
7
|
+
required: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface ToolOptions {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
parameters?: ParameterDefinition[];
|
|
13
|
+
authRequirements?: {
|
|
14
|
+
provider: string;
|
|
15
|
+
scopeBundle: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Decorator to register a function as an Opal tool
|
|
21
|
+
* @param options Tool options including:
|
|
22
|
+
* - name: Name of the tool
|
|
23
|
+
* - description: Description of the tool
|
|
24
|
+
* - authRequirements: (Optional) Authentication requirements
|
|
25
|
+
* Format: { provider: "oauth_provider", scopeBundle: "permissions_scope", required: true }
|
|
26
|
+
* Example: { provider: "google", scopeBundle: "calendar", required: true }
|
|
27
|
+
*
|
|
28
|
+
* Note: If your tool requires authentication, define your handler function with two parameters:
|
|
29
|
+
* ```
|
|
30
|
+
* async function myTool(parameters: ParameterInterface, authData?: any): Promise<any> {
|
|
31
|
+
* // Your tool implementation
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function tool(options: ToolOptions): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => any;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tool = tool;
|
|
4
|
+
require("reflect-metadata");
|
|
5
|
+
const models_1 = require("./models");
|
|
6
|
+
const registry_1 = require("./registry");
|
|
7
|
+
/**
|
|
8
|
+
* Map a TypeScript type to a ParameterType
|
|
9
|
+
* @param type TypeScript type
|
|
10
|
+
*/
|
|
11
|
+
function mapTypeToParameterType(type) {
|
|
12
|
+
if (type === String || type.name === 'String') {
|
|
13
|
+
return models_1.ParameterType.String;
|
|
14
|
+
}
|
|
15
|
+
else if (type === Number || type.name === 'Number') {
|
|
16
|
+
return models_1.ParameterType.Number;
|
|
17
|
+
}
|
|
18
|
+
else if (type === Boolean || type.name === 'Boolean') {
|
|
19
|
+
return models_1.ParameterType.Boolean;
|
|
20
|
+
}
|
|
21
|
+
else if (type === Array || type.name === 'Array') {
|
|
22
|
+
return models_1.ParameterType.List;
|
|
23
|
+
}
|
|
24
|
+
else if (type === Object || type.name === 'Object') {
|
|
25
|
+
return models_1.ParameterType.Dictionary;
|
|
26
|
+
}
|
|
27
|
+
// Default to string
|
|
28
|
+
return models_1.ParameterType.String;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Extract parameters from a TypeScript interface
|
|
32
|
+
* @param paramType Parameter type object
|
|
33
|
+
*/
|
|
34
|
+
function extractParameters(paramType) {
|
|
35
|
+
const parameters = [];
|
|
36
|
+
// This is very basic and doesn't handle complex types
|
|
37
|
+
// For production use, this would need to be more sophisticated
|
|
38
|
+
for (const key in paramType) {
|
|
39
|
+
if (paramType.hasOwnProperty(key)) {
|
|
40
|
+
const type = typeof paramType[key] === 'undefined' ? String : paramType[key].constructor;
|
|
41
|
+
const required = true; // In a real implementation, we'd detect optional parameters
|
|
42
|
+
parameters.push(new models_1.Parameter(key, mapTypeToParameterType(type), '', // Description - in a real impl we'd use TypeDoc or similar
|
|
43
|
+
required));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return parameters;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Decorator to register a function as an Opal tool
|
|
50
|
+
* @param options Tool options including:
|
|
51
|
+
* - name: Name of the tool
|
|
52
|
+
* - description: Description of the tool
|
|
53
|
+
* - authRequirements: (Optional) Authentication requirements
|
|
54
|
+
* Format: { provider: "oauth_provider", scopeBundle: "permissions_scope", required: true }
|
|
55
|
+
* Example: { provider: "google", scopeBundle: "calendar", required: true }
|
|
56
|
+
*
|
|
57
|
+
* Note: If your tool requires authentication, define your handler function with two parameters:
|
|
58
|
+
* ```
|
|
59
|
+
* async function myTool(parameters: ParameterInterface, authData?: any): Promise<any> {
|
|
60
|
+
* // Your tool implementation
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
function tool(options) {
|
|
65
|
+
return function (target, propertyKey, descriptor) {
|
|
66
|
+
const isMethod = propertyKey && descriptor;
|
|
67
|
+
const handler = isMethod ? descriptor.value : target;
|
|
68
|
+
// Generate endpoint from name - ensure hyphens instead of underscores
|
|
69
|
+
const endpoint = `/tools/${options.name.replace(/_/g, '-')}`;
|
|
70
|
+
// Convert parameter definitions to Parameter objects
|
|
71
|
+
const parameters = [];
|
|
72
|
+
if (options.parameters && options.parameters.length > 0) {
|
|
73
|
+
// Use the explicitly provided parameter definitions
|
|
74
|
+
for (const paramDef of options.parameters) {
|
|
75
|
+
parameters.push(new models_1.Parameter(paramDef.name, paramDef.type, paramDef.description, paramDef.required));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Create auth requirements if specified
|
|
79
|
+
let authRequirements;
|
|
80
|
+
if (options.authRequirements) {
|
|
81
|
+
authRequirements = [
|
|
82
|
+
new models_1.AuthRequirement(options.authRequirements.provider, options.authRequirements.scopeBundle, options.authRequirements.required ?? true)
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
// Register the tool with all services
|
|
86
|
+
for (const service of registry_1.registry.services) {
|
|
87
|
+
service.registerTool(options.name, options.description, handler, parameters, endpoint, authRequirements);
|
|
88
|
+
}
|
|
89
|
+
return isMethod ? descriptor : target;
|
|
90
|
+
};
|
|
91
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.requiresAuth = exports.tool = exports.ToolsService = void 0;
|
|
18
|
+
require("reflect-metadata");
|
|
19
|
+
var service_1 = require("./service");
|
|
20
|
+
Object.defineProperty(exports, "ToolsService", { enumerable: true, get: function () { return service_1.ToolsService; } });
|
|
21
|
+
var decorators_1 = require("./decorators");
|
|
22
|
+
Object.defineProperty(exports, "tool", { enumerable: true, get: function () { return decorators_1.tool; } });
|
|
23
|
+
var auth_1 = require("./auth");
|
|
24
|
+
Object.defineProperty(exports, "requiresAuth", { enumerable: true, get: function () { return auth_1.requiresAuth; } });
|
|
25
|
+
__exportStar(require("./models"), exports);
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types of parameters supported by Opal tools
|
|
3
|
+
*/
|
|
4
|
+
export declare enum ParameterType {
|
|
5
|
+
String = "string",
|
|
6
|
+
Integer = "integer",
|
|
7
|
+
Number = "number",
|
|
8
|
+
Boolean = "boolean",
|
|
9
|
+
List = "list",
|
|
10
|
+
Dictionary = "object"
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Parameter definition for an Opal tool
|
|
14
|
+
*/
|
|
15
|
+
export declare class Parameter {
|
|
16
|
+
name: string;
|
|
17
|
+
type: ParameterType;
|
|
18
|
+
description: string;
|
|
19
|
+
required: boolean;
|
|
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: string, type: ParameterType, description: string, required: boolean);
|
|
28
|
+
/**
|
|
29
|
+
* Convert to JSON for the discovery endpoint
|
|
30
|
+
*/
|
|
31
|
+
toJSON(): {
|
|
32
|
+
name: string;
|
|
33
|
+
type: ParameterType;
|
|
34
|
+
description: string;
|
|
35
|
+
required: boolean;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Authentication requirements for an Opal tool
|
|
40
|
+
*/
|
|
41
|
+
export declare class AuthRequirement {
|
|
42
|
+
provider: string;
|
|
43
|
+
scopeBundle: string;
|
|
44
|
+
required: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Create a new authentication requirement
|
|
47
|
+
* @param provider Auth provider (e.g., "google", "microsoft")
|
|
48
|
+
* @param scopeBundle Scope bundle (e.g., "calendar", "drive")
|
|
49
|
+
* @param required Whether authentication is required
|
|
50
|
+
*/
|
|
51
|
+
constructor(provider: string, scopeBundle: string, required?: boolean);
|
|
52
|
+
/**
|
|
53
|
+
* Convert to JSON for the discovery endpoint
|
|
54
|
+
*/
|
|
55
|
+
toJSON(): {
|
|
56
|
+
provider: string;
|
|
57
|
+
scope_bundle: string;
|
|
58
|
+
required: boolean;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Function definition for an Opal tool
|
|
63
|
+
*/
|
|
64
|
+
export declare class Function {
|
|
65
|
+
name: string;
|
|
66
|
+
description: string;
|
|
67
|
+
parameters: Parameter[];
|
|
68
|
+
endpoint: string;
|
|
69
|
+
authRequirements?: AuthRequirement[] | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* HTTP method for the endpoint (default: POST)
|
|
72
|
+
*/
|
|
73
|
+
httpMethod: string;
|
|
74
|
+
/**
|
|
75
|
+
* Create a new function definition
|
|
76
|
+
* @param name Function name
|
|
77
|
+
* @param description Function description
|
|
78
|
+
* @param parameters Function parameters
|
|
79
|
+
* @param endpoint API endpoint
|
|
80
|
+
* @param authRequirements Authentication requirements (optional)
|
|
81
|
+
*/
|
|
82
|
+
constructor(name: string, description: string, parameters: Parameter[], endpoint: string, authRequirements?: AuthRequirement[] | undefined);
|
|
83
|
+
/**
|
|
84
|
+
* Convert to JSON for the discovery endpoint
|
|
85
|
+
*/
|
|
86
|
+
toJSON(): any;
|
|
87
|
+
}
|
package/dist/models.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Function = exports.AuthRequirement = exports.Parameter = exports.ParameterType = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Types of parameters supported by Opal tools
|
|
6
|
+
*/
|
|
7
|
+
var ParameterType;
|
|
8
|
+
(function (ParameterType) {
|
|
9
|
+
ParameterType["String"] = "string";
|
|
10
|
+
ParameterType["Integer"] = "integer";
|
|
11
|
+
ParameterType["Number"] = "number";
|
|
12
|
+
ParameterType["Boolean"] = "boolean";
|
|
13
|
+
ParameterType["List"] = "list";
|
|
14
|
+
ParameterType["Dictionary"] = "object";
|
|
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
|
+
/**
|
|
47
|
+
* Authentication requirements for an Opal tool
|
|
48
|
+
*/
|
|
49
|
+
class AuthRequirement {
|
|
50
|
+
/**
|
|
51
|
+
* Create a new authentication requirement
|
|
52
|
+
* @param provider Auth provider (e.g., "google", "microsoft")
|
|
53
|
+
* @param scopeBundle Scope bundle (e.g., "calendar", "drive")
|
|
54
|
+
* @param required Whether authentication is required
|
|
55
|
+
*/
|
|
56
|
+
constructor(provider, scopeBundle, required = true) {
|
|
57
|
+
this.provider = provider;
|
|
58
|
+
this.scopeBundle = scopeBundle;
|
|
59
|
+
this.required = required;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Convert to JSON for the discovery endpoint
|
|
63
|
+
*/
|
|
64
|
+
toJSON() {
|
|
65
|
+
return {
|
|
66
|
+
provider: this.provider,
|
|
67
|
+
scope_bundle: this.scopeBundle,
|
|
68
|
+
required: this.required
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.AuthRequirement = AuthRequirement;
|
|
73
|
+
/**
|
|
74
|
+
* Function definition for an Opal tool
|
|
75
|
+
*/
|
|
76
|
+
class Function {
|
|
77
|
+
/**
|
|
78
|
+
* Create a new function definition
|
|
79
|
+
* @param name Function name
|
|
80
|
+
* @param description Function description
|
|
81
|
+
* @param parameters Function parameters
|
|
82
|
+
* @param endpoint API endpoint
|
|
83
|
+
* @param authRequirements Authentication requirements (optional)
|
|
84
|
+
*/
|
|
85
|
+
constructor(name, description, parameters, endpoint, authRequirements) {
|
|
86
|
+
this.name = name;
|
|
87
|
+
this.description = description;
|
|
88
|
+
this.parameters = parameters;
|
|
89
|
+
this.endpoint = endpoint;
|
|
90
|
+
this.authRequirements = authRequirements;
|
|
91
|
+
/**
|
|
92
|
+
* HTTP method for the endpoint (default: POST)
|
|
93
|
+
*/
|
|
94
|
+
this.httpMethod = 'POST';
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Convert to JSON for the discovery endpoint
|
|
98
|
+
*/
|
|
99
|
+
toJSON() {
|
|
100
|
+
const result = {
|
|
101
|
+
name: this.name,
|
|
102
|
+
description: this.description,
|
|
103
|
+
parameters: this.parameters.map(p => p.toJSON()),
|
|
104
|
+
endpoint: this.endpoint,
|
|
105
|
+
http_method: this.httpMethod
|
|
106
|
+
};
|
|
107
|
+
if (this.authRequirements && this.authRequirements.length > 0) {
|
|
108
|
+
result.auth_requirements = this.authRequirements.map(auth => auth.toJSON());
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.Function = Function;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Express } from 'express';
|
|
2
|
+
import { AuthRequirement, Parameter } from './models';
|
|
3
|
+
export declare class ToolsService {
|
|
4
|
+
private app;
|
|
5
|
+
private router;
|
|
6
|
+
private functions;
|
|
7
|
+
/**
|
|
8
|
+
* Initialize a new tools service
|
|
9
|
+
* @param app Express application
|
|
10
|
+
*/
|
|
11
|
+
constructor(app: Express);
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the discovery endpoint
|
|
14
|
+
*/
|
|
15
|
+
private initRoutes;
|
|
16
|
+
/**
|
|
17
|
+
* Register a tool function
|
|
18
|
+
* @param name Tool name
|
|
19
|
+
* @param description Tool description
|
|
20
|
+
* @param handler Function implementing the tool
|
|
21
|
+
* @param parameters List of parameters for the tool
|
|
22
|
+
* @param endpoint API endpoint for the tool
|
|
23
|
+
* @param authRequirements Authentication requirements (optional)
|
|
24
|
+
*/
|
|
25
|
+
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;
|
|
27
|
+
}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ToolsService = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const models_1 = require("./models");
|
|
9
|
+
const registry_1 = require("./registry");
|
|
10
|
+
class ToolsService {
|
|
11
|
+
/**
|
|
12
|
+
* Initialize a new tools service
|
|
13
|
+
* @param app Express application
|
|
14
|
+
*/
|
|
15
|
+
constructor(app) {
|
|
16
|
+
this.functions = [];
|
|
17
|
+
this.app = app;
|
|
18
|
+
this.router = express_1.default.Router();
|
|
19
|
+
this.initRoutes();
|
|
20
|
+
// Register this service in the global registry
|
|
21
|
+
registry_1.registry.services.push(this);
|
|
22
|
+
}
|
|
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
|
+
/**
|
|
33
|
+
* Register a tool function
|
|
34
|
+
* @param name Tool name
|
|
35
|
+
* @param description Tool description
|
|
36
|
+
* @param handler Function implementing the tool
|
|
37
|
+
* @param parameters List of parameters for the tool
|
|
38
|
+
* @param endpoint API endpoint for the tool
|
|
39
|
+
* @param authRequirements Authentication requirements (optional)
|
|
40
|
+
*/
|
|
41
|
+
registerTool(name, description, handler, // Changed from Function to any to avoid confusion with built-in Function type
|
|
42
|
+
parameters, endpoint, authRequirements) {
|
|
43
|
+
const func = new models_1.Function(name, description, parameters, endpoint, authRequirements);
|
|
44
|
+
this.functions.push(func);
|
|
45
|
+
// Register the actual endpoint
|
|
46
|
+
this.router.post(endpoint, async (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
console.log(`Received request for ${endpoint}:`, req.body);
|
|
49
|
+
// Extract parameters from the request body
|
|
50
|
+
let params;
|
|
51
|
+
if (req.body && req.body.parameters) {
|
|
52
|
+
// Standard format: { "parameters": { ... } }
|
|
53
|
+
params = req.body.parameters;
|
|
54
|
+
console.log(`Extracted parameters from 'parameters' key:`, params);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Fallback for direct testing: { "name": "value" }
|
|
58
|
+
console.log(`Warning: 'parameters' key not found in request body. Using body directly.`);
|
|
59
|
+
params = req.body;
|
|
60
|
+
}
|
|
61
|
+
// Extract auth data if available
|
|
62
|
+
const authData = req.body && req.body.auth;
|
|
63
|
+
if (authData) {
|
|
64
|
+
console.log(`Auth data provided for provider: ${authData.provider || 'unknown'}`);
|
|
65
|
+
}
|
|
66
|
+
// Call the handler with extracted parameters and auth data
|
|
67
|
+
// Check if handler accepts auth as third parameter
|
|
68
|
+
const handlerParamCount = handler.length;
|
|
69
|
+
let result;
|
|
70
|
+
if (handlerParamCount >= 2) {
|
|
71
|
+
// Handler accepts auth data
|
|
72
|
+
result = await handler(params, authData);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Handler doesn't accept auth data
|
|
76
|
+
result = await handler(params);
|
|
77
|
+
}
|
|
78
|
+
console.log(`Tool ${name} returned:`, result);
|
|
79
|
+
res.json(result);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error(`Error in tool ${name}:`, error);
|
|
83
|
+
res.status(500).json({ error: error.message || 'Unknown error' });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.ToolsService = ToolsService;
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@optimizely-opal/opal-tools-sdk",
|
|
3
|
+
"version": "0.1.0-dev",
|
|
4
|
+
"description": "SDK for creating Opal-compatible tools services",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"opal",
|
|
14
|
+
"tools",
|
|
15
|
+
"sdk",
|
|
16
|
+
"ai",
|
|
17
|
+
"llm"
|
|
18
|
+
],
|
|
19
|
+
"author": "Optimizely",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"express": "^4.18.2",
|
|
23
|
+
"axios": "^1.6.0",
|
|
24
|
+
"reflect-metadata": "^0.1.13"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/express": "^4.17.17",
|
|
28
|
+
"@types/jest": "^29.5.3",
|
|
29
|
+
"@types/node": "^20.4.5",
|
|
30
|
+
"jest": "^29.6.2",
|
|
31
|
+
"ts-jest": "^29.1.1",
|
|
32
|
+
"typescript": "^5.1.6"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"express": "^4.18.2"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/optimizely/opal-tools-sdk.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/optimizely/opal-tools-sdk/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/optimizely/opal-tools-sdk#readme"
|
|
45
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware to handle authentication requirements
|
|
3
|
+
* @param req Express request
|
|
4
|
+
* @param res Express response
|
|
5
|
+
* @param next Express next function
|
|
6
|
+
*/
|
|
7
|
+
export function authMiddleware(req: any, res: any, next: any) {
|
|
8
|
+
const authHeader = req.headers.authorization;
|
|
9
|
+
if (!authHeader && req.authRequired) {
|
|
10
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// The Tools Management Service will provide the appropriate token
|
|
14
|
+
// in the Authorization header
|
|
15
|
+
next();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface AuthOptions {
|
|
19
|
+
provider: string;
|
|
20
|
+
scopeBundle: string;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decorator to indicate that a tool requires authentication
|
|
26
|
+
* @param options Authentication options
|
|
27
|
+
*/
|
|
28
|
+
export function requiresAuth(options: AuthOptions) {
|
|
29
|
+
return function(target: any, propertyKey?: string, descriptor?: PropertyDescriptor) {
|
|
30
|
+
const isMethod = propertyKey && descriptor;
|
|
31
|
+
|
|
32
|
+
if (isMethod && descriptor) {
|
|
33
|
+
const originalMethod = descriptor.value;
|
|
34
|
+
|
|
35
|
+
descriptor.value = function(...args: any[]) {
|
|
36
|
+
// Store auth requirements in function metadata
|
|
37
|
+
const fn = originalMethod as any;
|
|
38
|
+
fn.__authRequirements__ = {
|
|
39
|
+
provider: options.provider,
|
|
40
|
+
scopeBundle: options.scopeBundle,
|
|
41
|
+
required: options.required ?? true
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return originalMethod.apply(this, args);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return descriptor;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return target;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { ParameterType, Parameter, AuthRequirement } from './models';
|
|
3
|
+
import { registry } from './registry';
|
|
4
|
+
|
|
5
|
+
interface ParameterDefinition {
|
|
6
|
+
name: string;
|
|
7
|
+
type: ParameterType;
|
|
8
|
+
description: string;
|
|
9
|
+
required: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ToolOptions {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
parameters?: ParameterDefinition[];
|
|
16
|
+
authRequirements?: {
|
|
17
|
+
provider: string;
|
|
18
|
+
scopeBundle: string;
|
|
19
|
+
required?: boolean;
|
|
20
|
+
};
|
|
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;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Decorator to register a function as an Opal tool
|
|
72
|
+
* @param options Tool options including:
|
|
73
|
+
* - name: Name of the tool
|
|
74
|
+
* - description: Description of the tool
|
|
75
|
+
* - authRequirements: (Optional) Authentication requirements
|
|
76
|
+
* Format: { provider: "oauth_provider", scopeBundle: "permissions_scope", required: true }
|
|
77
|
+
* Example: { provider: "google", scopeBundle: "calendar", required: true }
|
|
78
|
+
*
|
|
79
|
+
* Note: If your tool requires authentication, define your handler function with two parameters:
|
|
80
|
+
* ```
|
|
81
|
+
* async function myTool(parameters: ParameterInterface, authData?: any): Promise<any> {
|
|
82
|
+
* // Your tool implementation
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export function tool(options: ToolOptions) {
|
|
87
|
+
return function(target: any, propertyKey?: string, descriptor?: PropertyDescriptor) {
|
|
88
|
+
const isMethod = propertyKey && descriptor;
|
|
89
|
+
const handler = isMethod ? descriptor.value : target;
|
|
90
|
+
|
|
91
|
+
// Generate endpoint from name - ensure hyphens instead of underscores
|
|
92
|
+
const endpoint = `/tools/${options.name.replace(/_/g, '-')}`;
|
|
93
|
+
|
|
94
|
+
// Convert parameter definitions to Parameter objects
|
|
95
|
+
const parameters: Parameter[] = [];
|
|
96
|
+
if (options.parameters && options.parameters.length > 0) {
|
|
97
|
+
// Use the explicitly provided parameter definitions
|
|
98
|
+
for (const paramDef of options.parameters) {
|
|
99
|
+
parameters.push(new Parameter(
|
|
100
|
+
paramDef.name,
|
|
101
|
+
paramDef.type,
|
|
102
|
+
paramDef.description,
|
|
103
|
+
paramDef.required
|
|
104
|
+
));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Create auth requirements if specified
|
|
109
|
+
let authRequirements: AuthRequirement[] | undefined;
|
|
110
|
+
if (options.authRequirements) {
|
|
111
|
+
authRequirements = [
|
|
112
|
+
new AuthRequirement(
|
|
113
|
+
options.authRequirements.provider,
|
|
114
|
+
options.authRequirements.scopeBundle,
|
|
115
|
+
options.authRequirements.required ?? true
|
|
116
|
+
)
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Register the tool with all services
|
|
121
|
+
for (const service of registry.services) {
|
|
122
|
+
service.registerTool(
|
|
123
|
+
options.name,
|
|
124
|
+
options.description,
|
|
125
|
+
handler,
|
|
126
|
+
parameters,
|
|
127
|
+
endpoint,
|
|
128
|
+
authRequirements
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return isMethod ? descriptor : target;
|
|
133
|
+
};
|
|
134
|
+
}
|
package/src/index.ts
ADDED
package/src/models.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types of parameters supported by Opal tools
|
|
3
|
+
*/
|
|
4
|
+
export enum ParameterType {
|
|
5
|
+
String = 'string',
|
|
6
|
+
Integer = 'integer',
|
|
7
|
+
Number = 'number',
|
|
8
|
+
Boolean = 'boolean',
|
|
9
|
+
List = 'list',
|
|
10
|
+
Dictionary = 'object'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parameter definition for an Opal tool
|
|
15
|
+
*/
|
|
16
|
+
export class Parameter {
|
|
17
|
+
/**
|
|
18
|
+
* Create a new parameter definition
|
|
19
|
+
* @param name Parameter name
|
|
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
|
+
) {}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert to JSON for the discovery endpoint
|
|
33
|
+
*/
|
|
34
|
+
toJSON() {
|
|
35
|
+
return {
|
|
36
|
+
name: this.name,
|
|
37
|
+
type: this.type,
|
|
38
|
+
description: this.description,
|
|
39
|
+
required: this.required
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Authentication requirements for an Opal tool
|
|
46
|
+
*/
|
|
47
|
+
export class AuthRequirement {
|
|
48
|
+
/**
|
|
49
|
+
* Create a new authentication requirement
|
|
50
|
+
* @param provider Auth provider (e.g., "google", "microsoft")
|
|
51
|
+
* @param scopeBundle Scope bundle (e.g., "calendar", "drive")
|
|
52
|
+
* @param required Whether authentication is required
|
|
53
|
+
*/
|
|
54
|
+
constructor(
|
|
55
|
+
public provider: string,
|
|
56
|
+
public scopeBundle: string,
|
|
57
|
+
public required: boolean = true
|
|
58
|
+
) {}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Convert to JSON for the discovery endpoint
|
|
62
|
+
*/
|
|
63
|
+
toJSON() {
|
|
64
|
+
return {
|
|
65
|
+
provider: this.provider,
|
|
66
|
+
scope_bundle: this.scopeBundle,
|
|
67
|
+
required: this.required
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Function definition for an Opal tool
|
|
74
|
+
*/
|
|
75
|
+
export class Function {
|
|
76
|
+
/**
|
|
77
|
+
* HTTP method for the endpoint (default: POST)
|
|
78
|
+
*/
|
|
79
|
+
public httpMethod: string = 'POST';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a new function definition
|
|
83
|
+
* @param name Function name
|
|
84
|
+
* @param description Function description
|
|
85
|
+
* @param parameters Function parameters
|
|
86
|
+
* @param endpoint API endpoint
|
|
87
|
+
* @param authRequirements Authentication requirements (optional)
|
|
88
|
+
*/
|
|
89
|
+
constructor(
|
|
90
|
+
public name: string,
|
|
91
|
+
public description: string,
|
|
92
|
+
public parameters: Parameter[],
|
|
93
|
+
public endpoint: string,
|
|
94
|
+
public authRequirements?: AuthRequirement[]
|
|
95
|
+
) {}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Convert to JSON for the discovery endpoint
|
|
99
|
+
*/
|
|
100
|
+
toJSON() {
|
|
101
|
+
const result: any = {
|
|
102
|
+
name: this.name,
|
|
103
|
+
description: this.description,
|
|
104
|
+
parameters: this.parameters.map(p => p.toJSON()),
|
|
105
|
+
endpoint: this.endpoint,
|
|
106
|
+
http_method: this.httpMethod
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (this.authRequirements && this.authRequirements.length > 0) {
|
|
110
|
+
result.auth_requirements = this.authRequirements.map(auth => auth.toJSON());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
}
|
package/src/registry.ts
ADDED
package/src/service.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import express, { Express, Request, Response, Router } from 'express';
|
|
2
|
+
import { Function, AuthRequirement, Parameter } from './models';
|
|
3
|
+
import { registry } from './registry';
|
|
4
|
+
|
|
5
|
+
export class ToolsService {
|
|
6
|
+
private app: Express;
|
|
7
|
+
private router: Router;
|
|
8
|
+
private functions: Function[] = [];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initialize a new tools service
|
|
12
|
+
* @param app Express application
|
|
13
|
+
*/
|
|
14
|
+
constructor(app: Express) {
|
|
15
|
+
this.app = app;
|
|
16
|
+
this.router = express.Router();
|
|
17
|
+
this.initRoutes();
|
|
18
|
+
|
|
19
|
+
// Register this service in the global registry
|
|
20
|
+
registry.services.push(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initialize the discovery endpoint
|
|
25
|
+
*/
|
|
26
|
+
private initRoutes(): void {
|
|
27
|
+
this.router.get('/discovery', (req: Request, res: Response) => {
|
|
28
|
+
res.json({ functions: this.functions.map(f => f.toJSON()) });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
this.app.use(this.router);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register a tool function
|
|
36
|
+
* @param name Tool name
|
|
37
|
+
* @param description Tool description
|
|
38
|
+
* @param handler Function implementing the tool
|
|
39
|
+
* @param parameters List of parameters for the tool
|
|
40
|
+
* @param endpoint API endpoint for the tool
|
|
41
|
+
* @param authRequirements Authentication requirements (optional)
|
|
42
|
+
*/
|
|
43
|
+
registerTool(
|
|
44
|
+
name: string,
|
|
45
|
+
description: string,
|
|
46
|
+
handler: any, // Changed from Function to any to avoid confusion with built-in Function type
|
|
47
|
+
parameters: Parameter[],
|
|
48
|
+
endpoint: string,
|
|
49
|
+
authRequirements?: AuthRequirement[]
|
|
50
|
+
): void {
|
|
51
|
+
const func = new Function(name, description, parameters, endpoint, authRequirements);
|
|
52
|
+
this.functions.push(func);
|
|
53
|
+
|
|
54
|
+
// Register the actual endpoint
|
|
55
|
+
this.router.post(endpoint, async (req: Request, res: Response) => {
|
|
56
|
+
try {
|
|
57
|
+
console.log(`Received request for ${endpoint}:`, req.body);
|
|
58
|
+
|
|
59
|
+
// Extract parameters from the request body
|
|
60
|
+
let params;
|
|
61
|
+
if (req.body && req.body.parameters) {
|
|
62
|
+
// Standard format: { "parameters": { ... } }
|
|
63
|
+
params = req.body.parameters;
|
|
64
|
+
console.log(`Extracted parameters from 'parameters' key:`, params);
|
|
65
|
+
} else {
|
|
66
|
+
// Fallback for direct testing: { "name": "value" }
|
|
67
|
+
console.log(`Warning: 'parameters' key not found in request body. Using body directly.`);
|
|
68
|
+
params = req.body;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Extract auth data if available
|
|
72
|
+
const authData = req.body && req.body.auth;
|
|
73
|
+
if (authData) {
|
|
74
|
+
console.log(`Auth data provided for provider: ${authData.provider || 'unknown'}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Call the handler with extracted parameters and auth data
|
|
78
|
+
// Check if handler accepts auth as third parameter
|
|
79
|
+
const handlerParamCount = handler.length;
|
|
80
|
+
let result;
|
|
81
|
+
|
|
82
|
+
if (handlerParamCount >= 2) {
|
|
83
|
+
// Handler accepts auth data
|
|
84
|
+
result = await handler(params, authData);
|
|
85
|
+
} else {
|
|
86
|
+
// Handler doesn't accept auth data
|
|
87
|
+
result = await handler(params);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(`Tool ${name} returned:`, result);
|
|
91
|
+
res.json(result);
|
|
92
|
+
} catch (error: any) {
|
|
93
|
+
console.error(`Error in tool ${name}:`, error);
|
|
94
|
+
res.status(500).json({ error: error.message || 'Unknown error' });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"experimentalDecorators": true,
|
|
12
|
+
"emitDecoratorMetadata": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"],
|
|
15
|
+
"exclude": ["node_modules", "**/*.test.ts"]
|
|
16
|
+
}
|