|
| 1 | +/* |
| 2 | + * Copyright (c) 2025, salesforce.com, inc. |
| 3 | + * All rights reserved. |
| 4 | + * SPDX-License-Identifier: MIT |
| 5 | + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT |
| 6 | + */ |
| 7 | + |
| 8 | +import { StateType, StateDefinition } from '@langchain/langgraph'; |
| 9 | +import { PropertyMetadataCollection } from '../common/propertyMetadata.js'; |
| 10 | +import { Logger, createComponentLogger } from '../logging/logger.js'; |
| 11 | + |
| 12 | +/** |
| 13 | + * Conditional router edge to check whether all required properties have been collected. |
| 14 | + * |
| 15 | + * This router evaluates the workflow state to determine if all properties specified |
| 16 | + * in the required properties collection have been fulfilled (are truthy). Based on |
| 17 | + * this evaluation, it routes to either a "fulfilled" or "unfulfilled" node. |
| 18 | + * |
| 19 | + * @template TState - The state type for the workflow |
| 20 | + * |
| 21 | + * @example |
| 22 | + * ```typescript |
| 23 | + * const requiredProperties: PropertyMetadataCollection = { |
| 24 | + * platform: { |
| 25 | + * zodType: z.enum(['iOS', 'Android']), |
| 26 | + * description: 'Target platform', |
| 27 | + * friendlyName: 'platform', |
| 28 | + * }, |
| 29 | + * projectName: { |
| 30 | + * zodType: z.string(), |
| 31 | + * description: 'Project name', |
| 32 | + * friendlyName: 'project name', |
| 33 | + * }, |
| 34 | + * }; |
| 35 | + * |
| 36 | + * const router = new CheckPropertiesFulfilledRouter<State>( |
| 37 | + * 'continueWorkflow', // Node to route to when all properties are fulfilled |
| 38 | + * 'getUserInput', // Node to route to when properties are missing |
| 39 | + * requiredProperties |
| 40 | + * ); |
| 41 | + * |
| 42 | + * // Use in LangGraph workflow |
| 43 | + * workflow.addConditionalEdges('checkProperties', router.execute); |
| 44 | + * ``` |
| 45 | + */ |
| 46 | +export class CheckPropertiesFulfilledRouter<TState extends StateType<StateDefinition>> { |
| 47 | + private readonly propertiesFulfilledNodeName: string; |
| 48 | + private readonly propertiesUnfulfilledNodeName: string; |
| 49 | + private readonly requiredProperties: PropertyMetadataCollection; |
| 50 | + private readonly logger: Logger; |
| 51 | + |
| 52 | + /** |
| 53 | + * Creates a new CheckPropertiesFulfilledRouter. |
| 54 | + * |
| 55 | + * @param propertiesFulfilledNodeName - The name of the node to route to if all properties are fulfilled |
| 56 | + * @param propertiesUnfulfilledNodeName - The name of the node to route to if any property is unfulfilled |
| 57 | + * @param requiredProperties - Collection of properties that must be collected from user |
| 58 | + * @param logger - Optional logger instance for debugging and monitoring routing decisions. |
| 59 | + * If not provided, a default component logger will be created. |
| 60 | + */ |
| 61 | + constructor( |
| 62 | + propertiesFulfilledNodeName: string, |
| 63 | + propertiesUnfulfilledNodeName: string, |
| 64 | + requiredProperties: PropertyMetadataCollection, |
| 65 | + logger?: Logger |
| 66 | + ) { |
| 67 | + this.propertiesFulfilledNodeName = propertiesFulfilledNodeName; |
| 68 | + this.propertiesUnfulfilledNodeName = propertiesUnfulfilledNodeName; |
| 69 | + this.requiredProperties = requiredProperties; |
| 70 | + this.logger = logger || createComponentLogger('CheckPropertiesFulfilledRouter'); |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * Evaluates the state to determine the next node based on property fulfillment. |
| 75 | + * |
| 76 | + * This method checks each property in the required properties collection. |
| 77 | + * If any property is missing or falsy, it routes to the unfulfilled node. |
| 78 | + * Only if all properties are present and truthy does it route to the fulfilled node. |
| 79 | + * |
| 80 | + * @param state - The current workflow state |
| 81 | + * @returns The name of the next node to route to |
| 82 | + */ |
| 83 | + execute = (state: TState): string => { |
| 84 | + return this.getPropertyFulfillmentStatus(state); |
| 85 | + }; |
| 86 | + |
| 87 | + /** |
| 88 | + * Internal method to check property fulfillment status. |
| 89 | + * |
| 90 | + * Iterates through all required properties and checks if they exist |
| 91 | + * and are truthy in the state object. |
| 92 | + * |
| 93 | + * @param state - The current workflow state |
| 94 | + * @returns The name of the node to route to based on fulfillment status |
| 95 | + */ |
| 96 | + private getPropertyFulfillmentStatus(state: TState): string { |
| 97 | + const unfulfilledProperties: string[] = []; |
| 98 | + |
| 99 | + for (const propertyName of Object.keys(this.requiredProperties)) { |
| 100 | + if (!state[propertyName as keyof TState]) { |
| 101 | + unfulfilledProperties.push(propertyName); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + if (unfulfilledProperties.length > 0) { |
| 106 | + this.logger.debug('Properties not fulfilled, routing to unfulfilled node', { |
| 107 | + unfulfilledProperties, |
| 108 | + targetNode: this.propertiesUnfulfilledNodeName, |
| 109 | + totalRequired: Object.keys(this.requiredProperties).length, |
| 110 | + }); |
| 111 | + return this.propertiesUnfulfilledNodeName; |
| 112 | + } |
| 113 | + |
| 114 | + this.logger.debug('All properties fulfilled, routing to fulfilled node', { |
| 115 | + targetNode: this.propertiesFulfilledNodeName, |
| 116 | + totalProperties: Object.keys(this.requiredProperties).length, |
| 117 | + }); |
| 118 | + return this.propertiesFulfilledNodeName; |
| 119 | + } |
| 120 | +} |
0 commit comments