UNPKG

@xynehq/jaf

Version:

Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools

506 lines 16.6 kB
/** * JAF ADK Layer - Tool System * * Functional tool creation, execution, and integration utilities */ import { throwToolError, createToolError, ToolSource, ToolParameterType } from '../types'; // ========== Tool Creation ========== export const createFunctionTool = (config) => { const { name, description, execute, parameters = [], metadata } = config; const toolMetadata = { source: ToolSource.FUNCTION, version: '1.0.0', ...metadata }; const executor = async (params, context) => { try { // Validate parameters const validation = validateToolParameters(params, parameters); if (!validation.success) { return { success: false, error: `Parameter validation failed: ${validation.errors?.join(', ')}` }; } // Execute function with params and context const result = await execute(params, context); return { success: true, data: result, metadata: { executedAt: new Date() } }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error', metadata: { error, executedAt: new Date() } }; } }; return { name, description, parameters, execute: executor, metadata: toolMetadata }; }; // Legacy function signature for backward compatibility export const createFunctionToolLegacy = (name, description, func, parameters = [], metadata) => { return createFunctionTool({ name, description, execute: func, parameters, metadata }); }; export const createAsyncFunctionTool = (config) => { return createFunctionTool(config); }; // ========== OpenAPI Tool Generation ========== export const createOpenAPIToolset = async (spec) => { const tools = []; for (const [path, methods] of Object.entries(spec.paths)) { for (const [method, operation] of Object.entries(methods)) { if (isValidOperation(operation)) { const tool = await createToolFromOperation(path, method, operation, spec); tools.push(tool); } } } return tools; }; const createToolFromOperation = async (path, method, operation, spec) => { const name = operation.operationId || `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`; const description = operation.description || operation.summary || `${method.toUpperCase()} ${path}`; const parameters = extractParametersFromOperation(operation); const executor = async (params, context) => { try { const response = await executeOpenAPICall(path, method, params, spec); return { success: true, data: response, metadata: { path, method, executedAt: new Date() } }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'API call failed', metadata: { path, method, error, executedAt: new Date() } }; } }; return { name, description, parameters, execute: executor, metadata: { source: ToolSource.OPENAPI, version: spec.info.version, tags: ['api', 'openapi'] } }; }; const extractParametersFromOperation = (operation) => { const parameters = []; // Extract from parameters if (operation.parameters) { for (const param of operation.parameters) { parameters.push(convertOpenAPIParameter(param)); } } // Extract from request body if (operation.requestBody) { const content = operation.requestBody.content; if (content['application/json']?.schema) { const schema = content['application/json'].schema; if (schema.properties) { for (const [propName, propSchema] of Object.entries(schema.properties)) { parameters.push({ name: propName, type: mapOpenAPIType(propSchema.type || 'string'), description: propSchema.description || '', required: schema.required?.includes(propName) || false }); } } } } return parameters; }; const convertOpenAPIParameter = (param) => { return { name: param.name, type: mapOpenAPIType(param.schema.type || 'string'), description: param.description || '', required: param.required || false, default: param.schema.default }; }; const mapOpenAPIType = (openApiType) => { switch (openApiType) { case 'integer': case 'number': return 'number'; case 'boolean': return 'boolean'; case 'array': return 'array'; case 'object': return 'object'; default: return 'string'; } }; const executeOpenAPICall = async (path, method, params, spec) => { // This is a simplified implementation // In a real implementation, you'd use a proper HTTP client // and handle authentication, base URLs, etc. const baseUrl = 'https://api.example.com'; // Extract from spec const url = interpolatePath(path, params); const fullUrl = `${baseUrl}${url}`; const options = { method: method.toUpperCase(), headers: { 'Content-Type': 'application/json' } }; if (method.toUpperCase() !== 'GET') { options.body = JSON.stringify(params); } const response = await fetch(fullUrl, options); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); }; const interpolatePath = (path, params) => { let result = path; for (const [key, value] of Object.entries(params)) { result = result.replace(`{${key}}`, String(value)); } return result; }; const isValidOperation = (operation) => { return typeof operation === 'object' && operation !== null; }; // ========== External Tool Adapters ========== export const createCrewAIAdapter = (crewAITool) => { const name = crewAITool.name || 'crewai_tool'; const description = crewAITool.description || 'CrewAI tool adapter'; // Extract parameters from CrewAI tool const parameters = extractCrewAIParameters(crewAITool); const executor = async (params, context) => { try { const result = await crewAITool.run(params); return { success: true, data: result, metadata: { source: 'crewai', executedAt: new Date() } }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'CrewAI tool execution failed', metadata: { source: 'crewai', error, executedAt: new Date() } }; } }; return { name, description, parameters, execute: executor, metadata: { source: ToolSource.CREWAI, version: '1.0.0', tags: ['crewai', 'external'] } }; }; export const createLangChainAdapter = (langChainTool) => { const name = langChainTool.name || 'langchain_tool'; const description = langChainTool.description || 'LangChain tool adapter'; const parameters = extractLangChainParameters(langChainTool); const executor = async (params, context) => { try { const result = await langChainTool.call(params); return { success: true, data: result, metadata: { source: 'langchain', executedAt: new Date() } }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'LangChain tool execution failed', metadata: { source: 'langchain', error, executedAt: new Date() } }; } }; return { name, description, parameters, execute: executor, metadata: { source: ToolSource.LANGCHAIN, version: '1.0.0', tags: ['langchain', 'external'] } }; }; const extractCrewAIParameters = (crewAITool) => { // This is a simplified extraction // Real implementation would inspect the tool's schema return [ { name: 'input', type: 'string', description: 'Input for the CrewAI tool', required: true } ]; }; const extractLangChainParameters = (langChainTool) => { // This is a simplified extraction // Real implementation would inspect the tool's schema return [ { name: 'input', type: 'string', description: 'Input for the LangChain tool', required: true } ]; }; // ========== Tool Validation ========== export const validateTool = (tool) => { const errors = []; if (!tool.name || tool.name.trim().length === 0) { errors.push('Tool name is required'); } if (!tool.description || tool.description.trim().length === 0) { errors.push('Tool description is required'); } if (!Array.isArray(tool.parameters)) { errors.push('Tool parameters must be an array'); } if (typeof tool.execute !== 'function') { errors.push('Tool execute must be a function'); } // Validate parameters for (const param of tool.parameters) { const paramValidation = validateToolParameter(param); if (!paramValidation.success) { errors.push(`Parameter '${param.name}': ${paramValidation.errors?.join(', ')}`); } } if (errors.length > 0) { return { success: false, errors }; } return { success: true, data: tool }; }; export const validateToolParameter = (param) => { const errors = []; if (!param.name || param.name.trim().length === 0) { errors.push('Parameter name is required'); } if (!param.type) { errors.push('Parameter type is required'); } if (!['string', 'number', 'boolean', 'object', 'array'].includes(param.type)) { errors.push(`Invalid parameter type: ${param.type}`); } if (!param.description || param.description.trim().length === 0) { errors.push('Parameter description is required'); } if (errors.length > 0) { return { success: false, errors }; } return { success: true, data: param }; }; export const validateToolParameters = (params, paramSchema) => { const errors = []; // Check required parameters for (const param of paramSchema) { if (param.required && !(param.name in params)) { errors.push(`Required parameter '${param.name}' is missing`); } } // Validate parameter types for (const [name, value] of Object.entries(params)) { const paramDef = paramSchema.find(p => p.name === name); if (paramDef) { const typeValidation = validateParameterType(value, paramDef); if (!typeValidation) { errors.push(`Parameter '${name}' has invalid type (expected ${paramDef.type})`); } } } if (errors.length > 0) { return { success: false, errors }; } return { success: true, data: params }; }; const validateParameterType = (value, param) => { switch (param.type) { case 'string': return typeof value === 'string'; case 'number': return typeof value === 'number'; case 'boolean': return typeof value === 'boolean'; case 'object': return typeof value === 'object' && value !== null && !Array.isArray(value); case 'array': return Array.isArray(value); default: return false; } }; // ========== Tool Execution ========== export const executeTool = async (tool, params, context) => { try { // Validate tool const toolValidation = validateTool(tool); if (!toolValidation.success) { throwToolError(`Tool validation failed: ${toolValidation.errors?.join(', ')}`, tool.name); } // Execute tool const result = await tool.execute(params, context); return result; } catch (error) { if (error && typeof error === 'object' && error.name === 'ToolError') { throw error; } return { success: false, error: error instanceof Error ? error.message : 'Tool execution failed', metadata: { toolName: tool.name, error, executedAt: new Date() } }; } }; export const executeTools = async (tools, params, context) => { const results = {}; for (const tool of tools) { const toolParams = params[tool.name] || {}; results[tool.name] = await executeTool(tool, toolParams, context); } return results; }; // ========== Tool Utilities ========== export const getToolByName = (tools, name) => { return tools.find(tool => tool.name === name) || null; }; export const hasToolByName = (tools, name) => { return getToolByName(tools, name) !== null; }; export const filterToolsBySource = (tools, source) => { return tools.filter(tool => tool.metadata?.source === source); }; export const getToolNames = (tools) => { return tools.map(tool => tool.name); }; export const cloneTool = (tool) => { return { ...tool, parameters: [...tool.parameters], metadata: tool.metadata ? { ...tool.metadata } : undefined }; }; // Export createToolError from types for external use export { createToolError }; // ========== Built-in Tools ========== export const createEchoTool = () => { return createFunctionTool({ name: 'echo', description: 'Echoes back the input message', execute: (params) => { const typedParams = params; return typedParams.message; }, parameters: [ { name: 'message', type: ToolParameterType.STRING, description: 'The message to echo back', required: true } ] }); }; export const createCalculatorTool = () => { // Import safe math evaluator // eslint-disable-next-line @typescript-eslint/no-var-requires const { evaluateMathExpression } = require('../../utils/safe-math'); return createFunctionTool({ name: 'calculator', description: 'Performs safe mathematical calculations', execute: (params) => { const typedParams = params; try { // Use safe math parser instead of eval const result = evaluateMathExpression(typedParams.expression); return { result, expression: typedParams.expression }; } catch (error) { throw new Error(`Invalid expression: ${typedParams.expression}. ${error instanceof Error ? error.message : ''}`); } }, parameters: [ { name: 'expression', type: ToolParameterType.STRING, description: 'Mathematical expression to evaluate (e.g., "2 + 2", "sqrt(16)", "2^3")', required: true } ] }); }; export const createTimestampTool = () => { return createFunctionTool({ name: 'timestamp', description: 'Returns the current timestamp', execute: () => ({ timestamp: new Date().toISOString(), unix: Date.now() }), parameters: [] }); }; //# sourceMappingURL=index.js.map