UNPKG

@optimizely-opal/opal-tools-sdk

Version:

SDK for creating Opal-compatible tools services

135 lines (120 loc) 4.08 kB
import 'reflect-metadata'; import { ParameterType, Parameter, AuthRequirement } from './models'; import { registry } from './registry'; interface ParameterDefinition { name: string; type: ParameterType; description: string; required: boolean; } interface ToolOptions { name: string; description: string; parameters?: ParameterDefinition[]; authRequirements?: { provider: string; scopeBundle: string; required?: boolean; }; } /** * Map a TypeScript type to a ParameterType * @param type TypeScript type */ function mapTypeToParameterType(type: any): ParameterType { if (type === String || type.name === 'String') { return ParameterType.String; } else if (type === Number || type.name === 'Number') { return ParameterType.Number; } else if (type === Boolean || type.name === 'Boolean') { return ParameterType.Boolean; } else if (type === Array || type.name === 'Array') { return ParameterType.List; } else if (type === Object || type.name === 'Object') { return ParameterType.Dictionary; } // Default to string return ParameterType.String; } /** * Extract parameters from a TypeScript interface * @param paramType Parameter type object */ function extractParameters(paramType: any): Parameter[] { const parameters: Parameter[] = []; // This is very basic and doesn't handle complex types // For production use, this would need to be more sophisticated for (const key in paramType) { if (paramType.hasOwnProperty(key)) { const type = typeof paramType[key] === 'undefined' ? String : paramType[key].constructor; const required = true; // In a real implementation, we'd detect optional parameters parameters.push(new Parameter( key, mapTypeToParameterType(type), '', // Description - in a real impl we'd use TypeDoc or similar required )); } } return parameters; } /** * Decorator to register a function as an Opal tool * @param options Tool options including: * - name: Name of the tool * - description: Description of the tool * - authRequirements: (Optional) Authentication requirements * Format: { provider: "oauth_provider", scopeBundle: "permissions_scope", required: true } * Example: { provider: "google", scopeBundle: "calendar", required: true } * * Note: If your tool requires authentication, define your handler function with two parameters: * ``` * async function myTool(parameters: ParameterInterface, authData?: any): Promise<any> { * // Your tool implementation * } * ``` */ export function tool(options: ToolOptions) { return function(target: any, propertyKey?: string, descriptor?: PropertyDescriptor) { const isMethod = propertyKey && descriptor; const handler = isMethod ? descriptor.value : target; // Generate endpoint from name - ensure hyphens instead of underscores const endpoint = `/tools/${options.name.replace(/_/g, '-')}`; // Convert parameter definitions to Parameter objects const parameters: Parameter[] = []; if (options.parameters && options.parameters.length > 0) { // Use the explicitly provided parameter definitions for (const paramDef of options.parameters) { parameters.push(new Parameter( paramDef.name, paramDef.type, paramDef.description, paramDef.required )); } } // Create auth requirements if specified let authRequirements: AuthRequirement[] | undefined; if (options.authRequirements) { authRequirements = [ new AuthRequirement( options.authRequirements.provider, options.authRequirements.scopeBundle, options.authRequirements.required ?? true ) ]; } // Register the tool with all services for (const service of registry.services) { service.registerTool( options.name, options.description, handler, parameters, endpoint, authRequirements ); } return isMethod ? descriptor : target; }; }