crewai-ts
Version:
TypeScript port of crewAI for agent-based workflows
261 lines (260 loc) • 10.7 kB
JavaScript
/**
* Decorators for flow control and method registration.
* Optimized for TypeScript with proper method metadata handling.
* Harmonized with Python crewAI implementation for API consistency.
*/
import { FlowValidationError } from './types.js';
/**
* Method decorator metadata symbol
* Using Symbol for memory efficiency and name collision avoidance
*/
const FLOW_METADATA = Symbol('flow:metadata');
/**
* Utility to safely get or create metadata object on methods
* - Performance optimized with metadata caching
* - Uses a WeakMap for memory efficiency with object references
*/
const metadataCache = new WeakMap();
function getMetadata(target) {
// Check cache first for performance optimization
if (metadataCache.has(target)) {
return metadataCache.get(target);
}
// Create new metadata if not found
const metadata = target[FLOW_METADATA] || {};
target[FLOW_METADATA] = metadata;
// Cache for future access (memory efficient as it uses weak references)
metadataCache.set(target, metadata);
return metadata;
}
/**
* Validates condition objects to ensure compatibility with Python implementation
* @param condition The condition to validate
* @throws FlowValidationError if condition is invalid
*/
function validateCondition(condition) {
if (typeof condition === 'string') {
// String conditions are simple method names - always valid
return;
}
if (typeof condition === 'function') {
// Function conditions are valid but with a compatibility warning
console.warn('Function conditions work differently between Python and TypeScript implementations. ' +
'For maximum compatibility, use string or object conditions.');
return;
}
if (typeof condition === 'object' && condition !== null) {
// Complex conditions must have valid type and methods
if (!('type' in condition) || !('methods' in condition)) {
throw new FlowValidationError('Complex condition must have type and methods properties');
}
if (condition.type !== 'AND' && condition.type !== 'OR') {
throw new FlowValidationError('Complex condition type must be \'AND\' or \'OR\'');
}
if (!Array.isArray(condition.methods) || condition.methods.length === 0) {
throw new FlowValidationError('Complex condition methods must be a non-empty array');
}
// Validate each method name is a string
for (const method of condition.methods) {
if (typeof method !== 'string') {
throw new FlowValidationError('All method names in condition must be strings');
}
}
return;
}
throw new FlowValidationError(`Invalid condition type: ${typeof condition}`);
}
/**
* Marks a method as a flow's starting point.
* Optimized for performance with metadata validation and caching.
*
* @param condition Optional condition that determines when the method starts
*/
export function start(condition) {
return function (target, propertyKey, descriptor) {
// Safety check: ensure descriptor and value exist
if (!descriptor || descriptor.value === undefined) {
console.warn(`Start decorator applied to ${String(propertyKey)} but descriptor or value is undefined`);
return descriptor;
}
// Performance optimization: capture original once
const originalMethod = descriptor.value;
// Validate condition if provided (ensures Python compatibility)
if (condition) {
validateCondition(condition);
}
// Get metadata with optimized caching
const metadata = getMetadata(originalMethod);
// Set start method metadata with type safety
metadata.isStartMethod = true;
if (condition) {
metadata.condition = condition;
}
// Store metadata on the method with efficient property access
originalMethod.__metadata = metadata;
return descriptor;
};
}
/**
* Creates a listener that executes when specified conditions are met.
* Performance optimized with condition validation for Python compatibility.
*
* @param condition Condition that triggers this listener
*/
export function listen(condition) {
// Input validation for better error messages
if (!condition) {
throw new FlowValidationError('listen() requires a condition parameter');
}
// Validate condition structure for compatibility
validateCondition(condition);
return function (target, propertyKey, descriptor) {
// Safety check: ensure descriptor and value exist
if (!descriptor || descriptor.value === undefined) {
console.warn(`Listen decorator applied to ${String(propertyKey)} but descriptor or value is undefined`);
return descriptor;
}
// Performance optimization: capture original once
const originalMethod = descriptor.value;
// Get metadata with optimized caching
const metadata = getMetadata(originalMethod);
// Set listener metadata with type safety
metadata.isListener = true;
metadata.condition = condition;
// Store metadata on the method with efficient property access
originalMethod.__metadata = metadata;
return descriptor;
};
}
/**
* Creates a routing method that directs flow execution based on conditions.
* Performance optimized with Python compatibility validation.
*
* @param condition Condition that triggers this router
*/
export function router(condition) {
// Input validation for better error messages
if (!condition) {
throw new FlowValidationError('router() requires a condition parameter');
}
// Validate condition structure for compatibility
validateCondition(condition);
return function (target, propertyKey, descriptor) {
// Safety check: ensure descriptor and value exist
if (!descriptor || descriptor.value === undefined) {
console.warn(`Listen decorator applied to ${String(propertyKey)} but descriptor or value is undefined`);
return descriptor;
}
// Performance optimization: capture original once
const originalMethod = descriptor.value;
// Get metadata with optimized caching
const metadata = getMetadata(originalMethod);
// Set router metadata with type safety
metadata.isRouter = true;
metadata.condition = condition;
// Add listener capabilities for triggering
metadata.isListener = true;
// Store metadata on the method with efficient property access
originalMethod.__metadata = metadata;
return descriptor;
};
}
/**
* Combines multiple conditions with OR logic for flow control.
* Creates a condition that is satisfied when any of the specified conditions are met.
* Performance optimized with proper validation for Python compatibility.
*
* @param conditions List of conditions to combine with OR logic
*/
export function or_(...conditions) {
// Validate input conditions
if (conditions.length === 0) {
throw new FlowValidationError('or_() requires at least one condition');
}
// Create a Set directly for O(1) lookups and automatic deduplication
const methodsSet = new Set();
// Process conditions with optimized type handling
for (const condition of conditions) {
try {
// Validate each condition for structure consistency
validateCondition(condition);
if (typeof condition === 'string') {
// Fast path for string conditions
methodsSet.add(condition);
}
else if (typeof condition === 'object' && condition !== null && 'methods' in condition) {
// Fast path for complex conditions
condition.methods.forEach(method => methodsSet.add(method));
}
else if (typeof condition === 'function') {
throw new FlowValidationError('Function conditions are not supported in or_() for cross-platform compatibility');
}
}
catch (error) {
// Enhanced error handling with context
if (error instanceof FlowValidationError) {
throw new FlowValidationError(`Invalid condition in or_(): ${error.message}`);
}
throw error;
}
}
// Convert set to array only once at the end for efficiency
return {
type: 'OR',
methods: Array.from(methodsSet)
};
}
/**
* Combines multiple conditions with AND logic for flow control.
* Creates a condition that is satisfied only when all specified conditions are met.
* Performance optimized with proper validation for Python compatibility.
*
* @param conditions List of conditions to combine with AND logic
*/
export function and_(...conditions) {
// Validate input conditions
if (conditions.length === 0) {
throw new FlowValidationError('and_() requires at least one condition');
}
// Create a Set directly for O(1) lookups and automatic deduplication
const methodsSet = new Set();
// Process conditions with optimized type handling
for (const condition of conditions) {
try {
// Validate each condition for structure consistency
validateCondition(condition);
if (typeof condition === 'string') {
// Fast path for string conditions
methodsSet.add(condition);
}
else if (typeof condition === 'object' && condition !== null && 'methods' in condition) {
// Fast path for complex conditions
condition.methods.forEach(method => methodsSet.add(method));
}
else if (typeof condition === 'function') {
throw new FlowValidationError('Function conditions are not supported in and_() for cross-platform compatibility');
}
}
catch (error) {
// Enhanced error handling with context
if (error instanceof FlowValidationError) {
throw new FlowValidationError(`Invalid condition in and_(): ${error.message}`);
}
throw error;
}
}
// Convert set to array only once at the end for efficiency
return {
type: 'AND',
methods: Array.from(methodsSet)
};
}
/**
* Aliases for Python compatibility
* These ensure the TypeScript API matches the Python naming conventions
*/
export const Start = start;
export const Listen = listen;
export const Router = router;
export const OR = or_;
export const AND = and_;