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

272 lines (271 loc) 13.4 kB
/** * Tool Routes * Endpoints for tool listing, discovery, and execution */ import { SpanStatusCode } from "@opentelemetry/api"; import { withSpan } from "../../telemetry/withSpan.js"; import { tracers } from "../../telemetry/tracers.js"; import { createErrorResponse, ToolArgumentsSchema, ToolExecuteRequestSchema, ToolNameParamSchema, validateParams, validateRequest, } from "../utils/validation.js"; /** * Create tool routes */ export function createToolRoutes(basePath = "/api") { return { prefix: `${basePath}/tools`, // IMPORTANT: Route ordering matters for proper matching! // Routes are matched in order, so literal/exact paths MUST come before // parameterized paths (e.g., :name) to prevent the parameter from // matching literal path segments like "search" or "execute". // Order: exact paths first, then paths with nested segments, then parameterized paths last. routes: [ // 1. GET /api/tools - List all tools (exact match, must be first) { method: "GET", path: `${basePath}/tools`, handler: async (ctx) => withSpan({ name: "neurolink.http.tools.list", tracer: tracers.http, attributes: { "http.route": `${basePath}/tools` }, }, async (span) => { const tools = await ctx.toolRegistry.listTools(); span.setAttribute("tools.count", tools.length); return { tools: tools.map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema, source: tool.source || "built-in", })), total: tools.length, }; }), description: "List all available tools", tags: ["tools"], }, // 2. GET /api/tools/search - Search tools (literal path segment) { method: "GET", path: `${basePath}/tools/search`, handler: async (ctx) => withSpan({ name: "neurolink.http.tools.search", tracer: tracers.http, attributes: { "http.route": `${basePath}/tools/search`, "tools.search.query": ctx.query.q ?? "", }, }, async () => { const { q, source, limit } = ctx.query; const tools = await ctx.toolRegistry.listTools(); let filtered = tools; // Filter by search query if (q) { const query = q.toLowerCase(); filtered = filtered.filter((tool) => tool.name.toLowerCase().includes(query) || (tool.description && tool.description.toLowerCase().includes(query))); } // Filter by source if (source) { filtered = filtered.filter((tool) => (tool.source || "built-in") === source); } // Apply limit const maxResults = limit ? parseInt(limit, 10) : 50; filtered = filtered.slice(0, maxResults); return { tools: filtered.map((tool) => ({ name: tool.name, description: tool.description, source: tool.source || "built-in", })), total: filtered.length, query: q || null, }; }), description: "Search tools by name or description", tags: ["tools"], }, // 3. POST /api/tools/execute - Execute tool by name in body (literal path segment) { method: "POST", path: `${basePath}/tools/execute`, handler: async (ctx) => { // Validate request body const validation = validateRequest(ToolExecuteRequestSchema, ctx.body, ctx.requestId); if (!validation.success) { return validation.error; } const request = validation.data; return withSpan({ name: "neurolink.http.tools.execute", tracer: tracers.http, attributes: { "http.route": `${basePath}/tools/execute`, "tool.name": request.name, }, }, async (span) => { const startTime = Date.now(); try { // Get tool from registry const tools = await ctx.toolRegistry.listTools(); const tool = tools.find((t) => t.name === request.name); if (!tool) { span.setAttribute("tool.found", false); span.setStatus({ code: SpanStatusCode.ERROR, message: `Tool '${request.name}' not found`, }); return { success: false, error: `Tool '${request.name}' not found`, duration: Date.now() - startTime, }; } // Execute the tool const result = await ctx.toolRegistry.executeTool(request.name, request.arguments); span.setAttribute("tool.success", true); return { success: true, data: result, duration: Date.now() - startTime, metadata: { toolName: request.name, sessionId: request.sessionId || null, }, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); span.setAttribute("tool.success", false); span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage, }); span.recordException(error instanceof Error ? error : new Error(errorMessage)); return { success: false, error: errorMessage, duration: Date.now() - startTime, }; } }); }, description: "Execute a tool with arguments", tags: ["tools"], }, // 4. POST /api/tools/:name/execute - Execute specific tool (parameterized with nested segment) { method: "POST", path: `${basePath}/tools/:name/execute`, handler: async (ctx) => { // Validate params const paramValidation = validateParams(ToolNameParamSchema, ctx.params, ctx.requestId); if (!paramValidation.success) { return paramValidation.error; } const { name } = paramValidation.data; // Validate body (tool arguments) const bodyValidation = validateRequest(ToolArgumentsSchema, ctx.body || {}, ctx.requestId); if (!bodyValidation.success) { return bodyValidation.error; } const args = bodyValidation.data; return withSpan({ name: "neurolink.http.tools.executeByName", tracer: tracers.http, attributes: { "http.route": `${basePath}/tools/:name/execute`, "tool.name": name, }, }, async (span) => { const startTime = Date.now(); try { // Get tool from registry const tools = await ctx.toolRegistry.listTools(); const tool = tools.find((t) => t.name === name); if (!tool) { span.setAttribute("tool.found", false); span.setStatus({ code: SpanStatusCode.ERROR, message: `Tool '${name}' not found`, }); return { success: false, error: `Tool '${name}' not found`, duration: Date.now() - startTime, }; } // Execute the tool const result = await ctx.toolRegistry.executeTool(name, args); span.setAttribute("tool.success", true); return { success: true, data: result, duration: Date.now() - startTime, metadata: { toolName: name, }, }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); span.setAttribute("tool.success", false); span.setStatus({ code: SpanStatusCode.ERROR, message: errorMessage, }); span.recordException(error instanceof Error ? error : new Error(errorMessage)); return { success: false, error: errorMessage, duration: Date.now() - startTime, }; } }); }, description: "Execute a specific tool by name", tags: ["tools"], }, // 5. GET /api/tools/:name - Get tool details (parameterized, MUST BE LAST) { method: "GET", path: `${basePath}/tools/:name`, handler: async (ctx) => { // Validate params const paramValidation = validateParams(ToolNameParamSchema, ctx.params, ctx.requestId); if (!paramValidation.success) { return paramValidation.error; } const { name } = paramValidation.data; return withSpan({ name: "neurolink.http.tools.get", tracer: tracers.http, attributes: { "http.route": `${basePath}/tools/:name`, "tool.name": name, }, }, async (span) => { const tools = await ctx.toolRegistry.listTools(); const tool = tools.find((t) => t.name === name); if (!tool) { span.setAttribute("tool.found", false); span.setStatus({ code: SpanStatusCode.ERROR, message: `Tool '${name}' not found`, }); return createErrorResponse("TOOL_NOT_FOUND", `Tool '${name}' not found`, undefined, ctx.requestId); } span.setAttribute("tool.found", true); return { name: tool.name, description: tool.description, inputSchema: tool.inputSchema, source: tool.source || "built-in", }; }); }, description: "Get tool details by name", tags: ["tools"], }, ], }; }