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

555 lines 21.1 kB
/** * Object Transformation Utilities * Centralizes repeated object transformation patterns to improve code reuse and maintainability */ import { logger } from "./logger.js"; import { inlineJsonSchema } from "./schemaConversion.js"; // ============================================================================ // TOOL EXECUTION TRANSFORMATIONS // ============================================================================ /** * Transform tool execution results from AI SDK format to NeuroLink GenerateResult format * Handles both single execution and array formats with robust type checking * * @param toolExecutions - Array of tool execution results from AI SDK (optional) * @returns Array of standardized tool execution objects with name, input, output, and duration * * @example * ```typescript * const executions = transformToolExecutions([ * { name: "calculator", input: { a: 5, b: 3 }, output: 8, duration: 150 } * ]); * // Returns: [{ name: "calculator", input: { a: 5, b: 3 }, output: 8, duration: 150 }] * ``` */ export function transformToolExecutions(toolExecutions) { if (!toolExecutions || !Array.isArray(toolExecutions)) { return []; } return toolExecutions.map((te, index) => { const teRecord = te; // Enhanced tool name extraction with multiple fallback strategies let toolName = teRecord.name || teRecord.toolName || teRecord.tool || ""; // If still no name, try to extract from nested objects if (!toolName && teRecord.toolCall && typeof teRecord.toolCall === "object") { const toolCall = teRecord.toolCall; toolName = toolCall.name || toolCall.toolName || ""; } // Last resort: use index-based fallback to avoid "Unknown Tool" if (!toolName) { toolName = `tool_execution_${index}`; } // Enhanced tool parameter extraction with structured logging const parameterExtractionData = { executionIndex: index, toolNameExtracted: toolName, primarySources: { hasInput: !!teRecord.input, hasParameters: !!teRecord.parameters, hasArgs: !!teRecord.args, inputType: typeof teRecord.input, parametersType: typeof teRecord.parameters, argsType: typeof teRecord.args, }, rawRecordKeys: Object.keys(teRecord), }; logger.debug("[TransformationUtils] Tool parameter extraction analysis", parameterExtractionData); // Enhanced input extraction let input = teRecord.input || teRecord.parameters || teRecord.args || {}; const primaryExtractionResult = { keysCount: Object.keys(input).length, keys: Object.keys(input), extractionSuccessful: Object.keys(input).length > 0, extractionSource: teRecord.input ? "input" : teRecord.parameters ? "parameters" : teRecord.args ? "args" : "empty", }; logger.debug("[TransformationUtils] Primary parameter extraction result", primaryExtractionResult); // Extract input from nested toolCall if available if (Object.keys(input).length === 0 && teRecord.toolCall && typeof teRecord.toolCall === "object") { const toolCall = teRecord.toolCall; const nestedExtractionData = { reason: "Primary extraction failed, checking nested toolCall", nestedSources: { hasInput: !!toolCall.input, hasParameters: !!toolCall.parameters, hasArgs: !!toolCall.args, }, toolCallKeys: Object.keys(toolCall), }; logger.debug("[TransformationUtils] Nested parameter extraction attempt", nestedExtractionData); input = toolCall.input || toolCall.parameters || toolCall.args || {}; const nestedExtractionResult = { keysCount: Object.keys(input).length, keys: Object.keys(input), extractionSuccessful: Object.keys(input).length > 0, extractionSource: toolCall.input ? "toolCall.input" : toolCall.parameters ? "toolCall.parameters" : toolCall.args ? "toolCall.args" : "empty", }; logger.debug("[TransformationUtils] Nested parameter extraction result", nestedExtractionResult); } // Target tool parameter analysis for critical tools if (toolName && (toolName.includes("SuccessRateSRByTime") || toolName.includes("juspay-analytics"))) { const targetToolAnalysis = { toolName, inputKeys: Object.keys(input), keysCount: Object.keys(input).length, hasStartTime: "startTime" in input, hasEndTime: "endTime" in input, startTimeValue: input.startTime || "MISSING", endTimeValue: input.endTime || "MISSING", extractionStatus: Object.keys(input).length === 0 ? "FAILED" : "SUCCESS", }; logger.debug("[TransformationUtils] Target tool parameter analysis", targetToolAnalysis); if (Object.keys(input).length === 0) { logger.error("[TransformationUtils] Critical: Target tool parameter extraction failed", { toolName, reason: "Both primary and nested extraction returned empty parameters", impact: "AI response did not contain expected parameter structure", }); } } // Final parameter extraction summary const finalExtractionSummary = { toolName, inputKeysCount: Object.keys(input).length, inputKeys: Object.keys(input), hasParameters: Object.keys(input).length > 0, duration: teRecord.duration ?? teRecord.executionTime ?? teRecord.responseTime ?? 0, hasOutput: !!(teRecord.output || teRecord.result || teRecord.response), }; logger.debug("[TransformationUtils] Final parameter extraction result", finalExtractionSummary); // Enhanced output extraction with success indication const output = teRecord.output || teRecord.result || teRecord.response || "success"; // Enhanced duration extraction const duration = teRecord.duration ?? teRecord.executionTime ?? teRecord.responseTime ?? 0; return { name: toolName, input: input, output: output, duration: duration, }; }); } /** * Transform tool execution results from AI SDK format to internal format (for MCP generation) * Used in tryMCPGeneration method */ export function transformToolExecutionsForMCP(toolExecutions) { if (!toolExecutions || !Array.isArray(toolExecutions)) { return []; } return toolExecutions.map((te, index) => { const teRecord = te; // Enhanced tool name extraction matching the main function let toolName = teRecord.name || teRecord.toolName || teRecord.tool || ""; // Try nested toolCall extraction if (!toolName && teRecord.toolCall && typeof teRecord.toolCall === "object") { const toolCall = teRecord.toolCall; toolName = toolCall.name || toolCall.toolName || ""; } // Fallback to avoid empty names if (!toolName) { toolName = `mcp_tool_execution_${index}`; } // Enhanced execution time extraction const executionTime = teRecord.duration ?? teRecord.executionTime ?? teRecord.responseTime ?? 0; // Enhanced success detection - check for actual success indicators let success = true; // Default to true // Check for explicit success/error indicators if (teRecord.success !== undefined) { success = Boolean(teRecord.success); } else if (teRecord.error !== undefined) { success = false; } else if (teRecord.status !== undefined) { const status = String(teRecord.status).toLowerCase().trim(); success = !["error", "failed", "failure", "fail"].includes(status); } // Enhanced server ID extraction let serverId = teRecord.serverId || teRecord.server || teRecord.source || undefined; // Try to extract from nested structures if (!serverId && teRecord.toolCall && typeof teRecord.toolCall === "object") { const toolCall = teRecord.toolCall; serverId = toolCall.serverId || toolCall.server || undefined; } return { toolName: toolName, executionTime: executionTime, success: success, serverId: serverId, }; }); } // ============================================================================ // AVAILABLE TOOLS TRANSFORMATIONS // ============================================================================ /** * Transform available tools from internal format to GenerateResult format * Ensures consistent tool information structure across the API with schema normalization * * @param availableTools - Array of tool definitions from various sources (MCP servers, builtin tools, etc.) * @returns Array of normalized tool descriptions with consistent schema format * * @example * ```typescript * const tools = transformAvailableTools([ * { name: "calculator", description: "Math tool", server: "builtin", inputSchema: {...} } * ]); * // Returns: [{ name: "calculator", description: "Math tool", serverId: "builtin", schema: {...} }] * ``` */ export function transformAvailableTools(availableTools) { if (!availableTools || !Array.isArray(availableTools)) { return []; } return availableTools.map((tool) => { const toolRecord = tool; let extractedParameters = {}; const inputSchema = toolRecord.inputSchema; const directParameters = toolRecord.parameters; const fallbackSchema = toolRecord.schema; if (inputSchema && typeof inputSchema === "object") { // Use shared inlineJsonSchema for recursive $ref resolution const inlinedSchema = inlineJsonSchema(inputSchema); if (inlinedSchema.properties) { extractedParameters = { type: inlinedSchema.type || "object", properties: inlinedSchema.properties, required: inlinedSchema.required || [], ...inlinedSchema, }; } else if (inlinedSchema.type === "object") { extractedParameters = inlinedSchema; } else { extractedParameters = inlinedSchema; } } else if (directParameters && typeof directParameters === "object") { // Also inline $ref for direct parameters if present extractedParameters = inlineJsonSchema(directParameters); } else if (fallbackSchema && typeof fallbackSchema === "object") { // Also inline $ref for fallback schema if present extractedParameters = inlineJsonSchema(fallbackSchema); } if (!extractedParameters || typeof extractedParameters !== "object") { extractedParameters = {}; } if (extractedParameters && !extractedParameters.type && extractedParameters.properties) { extractedParameters.type = "object"; } return { name: tool.name || "", description: tool.description || "", server: tool.server || "", parameters: extractedParameters, }; }); } /** * Transform tools for MCP generation format * Simple transformation for internal MCP tool lists */ export function transformToolsForMCP(availableTools) { return availableTools.map((tool) => ({ name: tool.name, description: tool.description, server: tool.server, category: tool.category, })); } /** * Transform tools to expected format with required properties * Used in getAllAvailableTools method for final output */ export function transformToolsToExpectedFormat(tools) { return tools.map((tool) => ({ name: tool.name, description: tool.description || "No description available", server: tool.serverId || "unknown", category: tool.category, inputSchema: tool.inputSchema, })); } // ============================================================================ // STRING AND ARRAY TRANSFORMATIONS // ============================================================================ /** * Extract tool names from tool objects * Common pattern for creating arrays of tool names */ export function extractToolNames(tools) { return tools.map((tool) => tool.name); } /** * Extract object keys as a comma-separated string * Common pattern for logging and debugging */ export function getKeysAsString(obj, fallback = "none") { const keys = Object.keys(obj); return keys.length > 0 ? keys.join(", ") : fallback; } /** * Count object properties * Common pattern for metrics and logging */ export function getKeyCount(obj) { return Object.keys(obj).length; } // ============================================================================ // SCHEMA TRANSFORMATIONS // ============================================================================ /** * Transform schema properties to parameter descriptions * Used in tool-aware system prompt generation */ export function transformSchemaToParameterDescription(schema) { if (!schema?.properties) { return ""; } const requiredParams = new Set(schema.required || []); return Object.entries(schema.properties) .map(([key, value]) => { const typedValue = value; const required = requiredParams.has(key) ? " (required)" : ""; const description = typedValue.description ? ` - ${typedValue.description}` : ""; return ` - ${key}: ${typedValue.type || "unknown"}${required}${description}`; }) .join("\n"); } /** * Transform tools to tool descriptions for system prompts * Consolidated pattern for creating tool-aware prompts */ export function transformToolsToDescriptions(availableTools) { return availableTools .map((tool) => { const schema = tool.inputSchema; let params = ""; if (schema?.properties) { params = transformSchemaToParameterDescription(schema); } return `- ${tool.name}: ${tool.description} (from ${tool.server})\n${params}`; }) .join("\n\n"); } // ============================================================================ // VALIDATION TRANSFORMATIONS // ============================================================================ /** * Transform parameters for validation * Common pattern when logging or checking parameter structures */ export function transformParamsForLogging(params) { if (!params || typeof params !== "object") { return String(params); } const record = params; return `${Object.keys(record).length} params`; } /** * Safe property extraction from unknown objects * Common pattern for safely accessing properties from unknown structures */ export function safeExtractProperty(obj, key, fallback) { if (!obj || typeof obj !== "object" || obj === null) { return fallback; } const record = obj; const value = record[key]; return value !== undefined ? value : fallback; } /** * Safe extraction of string properties * Specialized version for string properties with fallback */ export function safeExtractString(obj, key, fallback = "") { const value = safeExtractProperty(obj, key, fallback); return typeof value === "string" ? value : fallback; } /** * Safe extraction of number properties * Specialized version for number properties with fallback */ export function safeExtractNumber(obj, key, fallback = 0) { const value = safeExtractProperty(obj, key, fallback); return typeof value === "number" ? value : fallback; } /** * Safe extraction of boolean properties * Specialized version for boolean properties with fallback */ export function safeExtractBoolean(obj, key, fallback = false) { const value = safeExtractProperty(obj, key, fallback); return typeof value === "boolean" ? value : fallback; } // ============================================================================ // COLLECTION TRANSFORMATIONS // ============================================================================ /** * Transform Map to array of values * Common pattern for converting tool maps to arrays */ export function mapToArray(map) { return Array.from(map.values()); } /** * Transform Map to array of key-value pairs * Common pattern for processing map entries */ export function mapToEntries(map) { return Array.from(map.entries()); } /** * Group array items by a key * Common pattern for organizing tools or other objects by category */ export function groupBy(items, keyFn) { const groups = new Map(); for (const item of items) { const key = keyFn(item); const existing = groups.get(key) || []; existing.push(item); groups.set(key, existing); } return groups; } /** * Remove undefined properties from objects * Common pattern for cleaning up object structures */ export function removeUndefinedProperties(obj) { const cleaned = {}; for (const [key, value] of Object.entries(obj)) { if (value !== undefined) { cleaned[key] = value; } } return cleaned; } /** * Merge objects with undefined handling * Common pattern for combining configuration objects */ export function mergeWithUndefinedHandling(target, ...sources) { const result = { ...target }; for (const source of sources) { for (const [key, value] of Object.entries(source)) { if (value !== undefined) { result[key] = value; } } } return result; } // ============================================================================ // TOOL OPTIMIZATION UTILITIES // ============================================================================ /** * Optimize tool information for collection with minimal object creation * Consolidates repeated optimization patterns across different tool sources * * @param tool - Tool information to optimize * @param defaults - Default values to apply if missing * @returns Optimized tool with minimal object creation * * @example * ```typescript * const optimized = optimizeToolForCollection(tool, { * serverId: "builtin", * category: "math" * }); * ``` */ export function optimizeToolForCollection(tool, defaults) { // Check what properties actually need modification const needsDescription = !tool.description && defaults.description; const needsServerId = !tool.serverId && defaults.serverId; const needsCategory = !tool.category && defaults.category; const needsInputSchema = !tool.inputSchema && defaults.inputSchema; const hasParametersConflict = tool.inputSchema && "parameters" in tool; // Only create new object if modifications are actually needed if (!needsDescription && !needsServerId && !needsCategory && !needsInputSchema && !hasParametersConflict) { return tool; // Return original tool without modification } // Create optimized tool with only necessary changes const optimizedTool = { ...tool, ...(needsDescription && { description: defaults.description }), ...(needsServerId && { serverId: defaults.serverId }), ...(needsCategory && { category: defaults.category }), ...(needsInputSchema && { inputSchema: defaults.inputSchema }), }; // Clean up schema conflicts if present if (hasParametersConflict) { const cleanedTool = { ...optimizedTool }; delete cleanedTool.parameters; return cleanedTool; } return optimizedTool; } //# sourceMappingURL=transformationUtils.js.map