UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

399 lines (398 loc) 14.9 kB
/** * OpenAPI 3.1 Specification Generator * Generates OpenAPI documentation from NeuroLink server routes */ import { OpenAPISchemas } from "./schemas.js"; import { CommonParameters, createDeleteOperation, createGetOperation, createPostOperation, createStreamingPostOperation, DefaultServers, NeuroLinkApiInfo, StandardErrorResponses, StandardTags, BearerSecurityScheme, ApiKeySecurityScheme, } from "./templates.js"; // ============================================ // OpenAPI Generator Class // ============================================ /** * OpenAPI specification generator * Generates OpenAPI 3.1 compliant documentation from route definitions */ export class OpenAPIGenerator { config; routes = []; constructor(config = {}) { this.config = { includeSecurity: true, basePath: "/api", ...config, }; // Initialize routes from config if provided if (config.routes) { this.routes = [...config.routes]; } } /** * Add routes to the generator */ addRoutes(routes) { this.routes.push(...routes); } /** * Add a single route */ addRoute(route) { this.routes.push(route); } /** * Generate the OpenAPI specification */ generate() { const spec = { openapi: "3.1.0", info: this.generateInfo(), servers: this.generateServers(), tags: this.generateTags(), paths: this.generatePaths(), components: this.generateComponents(), }; if (this.config.includeSecurity) { spec.security = [{ bearerAuth: [] }, { apiKeyAuth: [] }]; } return spec; } /** * Generate OpenAPI spec as JSON string */ toJSON(pretty = true) { const spec = this.generate(); return pretty ? JSON.stringify(spec, null, 2) : JSON.stringify(spec); } /** * Generate OpenAPI spec as YAML string * Note: For full YAML support, use a YAML library */ toYAML() { const spec = this.generate(); return this.jsonToYaml(spec); } // ============================================ // Private Methods // ============================================ generateInfo() { const baseInfo = { ...NeuroLinkApiInfo }; if (this.config.info?.title) { baseInfo.title = this.config.info.title; } if (this.config.info?.version) { baseInfo.version = this.config.info.version; } if (this.config.info?.description) { baseInfo.description = this.config.info.description; } return baseInfo; } generateServers() { if (this.config.servers && this.config.servers.length > 0) { return this.config.servers.map((server) => ({ url: server.url, description: server.description ?? "Server", })); } return DefaultServers; } generateTags() { const tags = [...StandardTags]; if (this.config.additionalTags) { tags.push(...this.config.additionalTags.map((tag) => ({ name: tag.name, description: tag.description, }))); } return tags; } generatePaths() { const paths = {}; // Generate paths from registered routes for (const route of this.routes) { const openApiPath = this.convertPath(route.path); if (!paths[openApiPath]) { paths[openApiPath] = {}; } const method = route.method.toLowerCase(); paths[openApiPath][method] = this.generateOperation(route); } // Add default API paths if no routes registered if (Object.keys(paths).length === 0) { return this.generateDefaultPaths(); } return paths; } generateDefaultPaths() { const basePath = this.config.basePath ?? "/api"; return { // Agent endpoints [`${basePath}/agent/execute`]: { post: createPostOperation("Execute agent", "Execute an AI agent with the specified input and configuration", ["agent"], "AgentExecuteRequest", "AgentExecuteResponse"), }, [`${basePath}/agent/stream`]: { post: createStreamingPostOperation("Stream agent response", "Execute an AI agent and stream the response in real-time", ["agent", "streaming"], "AgentExecuteRequest"), }, [`${basePath}/agent/providers`]: { get: createGetOperation("List providers", "Get list of available AI providers", ["agent"], "ProviderInfo"), }, // Tool endpoints [`${basePath}/tools`]: { get: createGetOperation("List tools", "Get list of all available tools", ["tools"], "ToolListResponse", [CommonParameters.limitQuery, CommonParameters.searchQuery]), }, [`${basePath}/tools/{name}`]: { get: createGetOperation("Get tool", "Get details of a specific tool", ["tools"], "ToolDefinition", [CommonParameters.toolName]), }, [`${basePath}/tools/{name}/execute`]: { post: createPostOperation("Execute tool", "Execute a specific tool with provided arguments", ["tools"], "ToolExecuteRequest", "ToolExecuteResponse", [CommonParameters.toolName]), }, // MCP endpoints [`${basePath}/mcp/servers`]: { get: createGetOperation("List MCP servers", "Get list of all configured MCP servers", ["mcp"], "MCPServersListResponse"), }, [`${basePath}/mcp/servers/{name}`]: { get: createGetOperation("Get MCP server", "Get details of a specific MCP server", ["mcp"], "MCPServerStatus", [CommonParameters.serverName]), }, [`${basePath}/mcp/servers/{name}/tools`]: { get: createGetOperation("List MCP server tools", "Get tools available from a specific MCP server", ["mcp", "tools"], "ToolListResponse", [CommonParameters.serverName]), }, // Memory endpoints [`${basePath}/memory/sessions`]: { get: createGetOperation("List sessions", "Get list of all conversation sessions", ["memory"], "SessionsListResponse", [CommonParameters.limitQuery, CommonParameters.offsetQuery]), }, [`${basePath}/memory/sessions/{sessionId}`]: { get: createGetOperation("Get session", "Get a specific conversation session", ["memory"], "Session", [CommonParameters.sessionId]), delete: createDeleteOperation("Delete session", "Delete a conversation session", ["memory"], [CommonParameters.sessionId]), }, // Health endpoints [`${basePath}/health`]: { get: createGetOperation("Health check", "Check server health status", ["health"], "HealthResponse"), }, [`${basePath}/ready`]: { get: createGetOperation("Readiness check", "Check if server is ready to accept requests", ["health"], "ReadyResponse"), }, [`${basePath}/metrics`]: { get: createGetOperation("Get metrics", "Get server metrics and statistics", ["health"], "MetricsResponse"), }, }; } generateOperation(route) { const operation = { summary: route.description ?? `${route.method} ${route.path}`, tags: route.tags ?? ["general"], }; // Only include description if defined if (route.description) { operation.description = route.description; } // Add parameters from path const pathParams = this.extractPathParameters(route.path); if (pathParams.length > 0) { operation.parameters = pathParams; } // Add request body for POST/PUT/PATCH if (["POST", "PUT", "PATCH"].includes(route.method)) { if (route.requestSchema) { operation.requestBody = { required: true, content: { "application/json": { schema: route.requestSchema, }, }, }; } } // Add responses if (route.streaming?.enabled) { operation.responses = { "200": { description: "Streaming response", content: { "text/event-stream": { schema: { type: "string", }, }, }, }, ...StandardErrorResponses, }; } else { operation.responses = { "200": route.responseSchema ? { description: "Success", content: { "application/json": { schema: route.responseSchema, }, }, } : { description: "Success", content: { "application/json": { schema: { type: "object", }, }, }, }, ...StandardErrorResponses, }; } // Add security if route requires auth if (route.auth) { operation.security = [{ bearerAuth: [] }, { apiKeyAuth: [] }]; } return operation; } generateComponents() { const components = { schemas: { ...OpenAPISchemas, ...this.config.customSchemas, }, }; if (this.config.includeSecurity) { components.securitySchemes = { bearerAuth: BearerSecurityScheme, apiKeyAuth: ApiKeySecurityScheme, }; } // Add common parameters components.parameters = { sessionId: CommonParameters.sessionId, serverName: CommonParameters.serverName, toolName: CommonParameters.toolName, limit: CommonParameters.limitQuery, offset: CommonParameters.offsetQuery, search: CommonParameters.searchQuery, }; return components; } convertPath(path) { // Convert Express-style :param to OpenAPI {param} return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, "{$1}"); } extractPathParameters(path) { const params = []; const paramRegex = /:([a-zA-Z_][a-zA-Z0-9_]*)/g; let match; while ((match = paramRegex.exec(path)) !== null) { params.push({ name: match[1], in: "path", required: true, schema: { type: "string", }, }); } return params; } /** * Simple JSON to YAML converter * For production use, consider using a proper YAML library */ jsonToYaml(obj, indent = 0) { const spaces = " ".repeat(indent); if (obj === null || obj === undefined) { return "null"; } if (typeof obj === "boolean" || typeof obj === "number") { return String(obj); } if (typeof obj === "string") { // Check if string needs quotes if (obj.includes("\n") || obj.includes(":") || obj.includes("#") || obj.includes("'") || obj.includes('"') || obj.startsWith(" ") || obj.endsWith(" ")) { // Use block scalar for multiline if (obj.includes("\n")) { const lines = obj.split("\n"); return `|\n${lines.map((line) => spaces + " " + line).join("\n")}`; } return `"${obj.replace(/"/g, '\\"')}"`; } return obj; } if (Array.isArray(obj)) { if (obj.length === 0) { return "[]"; } return obj .map((item) => { const itemYaml = this.jsonToYaml(item, indent + 1); if (typeof item === "object" && item !== null) { return `${spaces}- ${itemYaml .trim() .replace(/^\s+/gm, (match) => spaces + " " + match.trim() + "\n") .trim()}`; } return `${spaces}- ${itemYaml}`; }) .join("\n"); } if (typeof obj === "object") { const entries = Object.entries(obj); if (entries.length === 0) { return "{}"; } return entries .map(([key, value]) => { const valueYaml = this.jsonToYaml(value, indent + 1); if (typeof value === "object" && value !== null && !Array.isArray(value)) { return `${spaces}${key}:\n${valueYaml}`; } if (Array.isArray(value)) { return `${spaces}${key}:\n${valueYaml}`; } return `${spaces}${key}: ${valueYaml}`; }) .join("\n"); } return String(obj); } } // ============================================ // Factory Functions // ============================================ /** * Create an OpenAPI generator with default configuration */ export function createOpenAPIGenerator(config) { return new OpenAPIGenerator(config); } /** * Generate OpenAPI spec from routes */ export function generateOpenAPISpec(routes, config) { const generator = new OpenAPIGenerator(config); generator.addRoutes(routes); return generator.generate(); } /** * Generate OpenAPI spec from server adapter configuration */ export function generateOpenAPIFromConfig(serverConfig, routes) { const generator = new OpenAPIGenerator({ basePath: serverConfig.basePath ?? "/api", servers: [ { url: `http://${serverConfig.host ?? "localhost"}:${serverConfig.port ?? 3000}`, description: "Configured server", }, ], }); if (routes) { generator.addRoutes(routes); } return generator.generate(); }