@xynehq/jaf
Version:
Juspay Agent Framework - A purely functional agent framework with immutable state and composable tools
506 lines • 16.6 kB
JavaScript
/**
* 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