Removed extraneous logs, and treat evaluator block as a pass-through. Added jsdoc annotations to executor functions

This commit is contained in:
Waleed Latif
2025-02-11 15:18:04 -08:00
parent 3d52dec731
commit 7ddb4ca0f0
+437 -401
View File
@@ -1,19 +1,3 @@
/**
* Executor for running agentic workflows in parallel.
*
* High-Level Overview:
* - This class is responsible for running workflows using a layered topological sort.
* - Blocks that have no unresolved dependencies are executed in parallel.
* - Depending on the block type (router, condition, agent, or regular tool), different execution
* logic is applied. For example, condition blocks evaluate multiple branches and record the
* chosen branch via its condition ID so that only that path is executed.
* - Each block's output is stored in the ExecutionContext so that subsequent blocks can reference them.
* - Detailed logs are collected for each block to assist with debugging.
*
* Error Handling:
* - If a block fails, an error is thrown, halting the workflow.
* - Meaningful error messages are provided.
*/
import { getAllBlocks } from '@/blocks'
import { generateEvaluatorPrompt } from '@/blocks/blocks/evaluator'
import { generateRouterPrompt } from '@/blocks/blocks/router'
@@ -25,6 +9,10 @@ import { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
import { executeTool, getTool } from '@/tools'
import { BlockLog, ExecutionContext, ExecutionResult, Tool } from './types'
/**
* Main executor class for running agentic workflows.
* Handles parallel execution, state management, and special block types.
*/
export class Executor {
constructor(
private workflow: SerializedWorkflow,
@@ -34,7 +22,11 @@ export class Executor {
) {}
/**
* Main entry point that executes the entire workflow in layered parallel fashion.
* Main entry point for workflow execution.
* Initializes context, executes blocks, and returns results.
*
* @param workflowId - Unique identifier for the workflow
* @returns Promise<ExecutionResult> - Execution results including success/failure, output, and logs
*/
async execute(workflowId: string): Promise<ExecutionResult> {
const startTime = new Date()
@@ -83,12 +75,16 @@ export class Executor {
}
/**
* Executes workflow blocks layer-by-layer. Blocks with no dependencies are processed together.
* Executes workflow blocks layer-by-layer in parallel, handling loops and conditional paths.
*
* Notes:
* - Maintains in-degrees and adjacency lists for blocks (i.e. dependencies).
* - Blocks with condition or router types update routing/conditional decisions.
* - Only the branch corresponding to the evaluated condition is executed.
* Key Features:
* - Executes blocks with no dependencies in parallel using topological sorting
* - Handles special blocks (router, evaluator, condition) and their path decisions
* - Manages feedback loops with iteration limits
* - Tracks and updates block states in the execution context
*
* @param context - The execution context containing block states and logs
* @returns Promise<BlockOutput> - The output of the last executed block
*/
private async executeInParallel(context: ExecutionContext): Promise<BlockOutput> {
const { blocks, connections } = this.workflow
@@ -348,10 +344,19 @@ export class Executor {
}
/**
* Executes a single block. Deduces the tool to call, validates parameters,
* makes the request, and transforms the response.
* Executes a single block with appropriate tool or provider.
* Handles different block types (router, evaluator, condition, agent).
*
* The result is logged and returned.
* Process:
* 1. Validates block state and configuration
* 2. Executes based on block type
* 3. Logs execution details
* 4. Stores results in context
*
* @param block - Block to execute
* @param inputs - Resolved inputs for the block
* @param context - Current execution context
* @returns Promise<BlockOutput> - Block execution results
*/
private async executeBlock(
block: SerializedBlock,
@@ -550,9 +555,413 @@ export class Executor {
}
/**
* Resolves template references in a block's configuration (e.g., "<blockId.property>"),
* as well as environment variables (format: "{{ENV_VAR}}").
* The values are pulled from the context's blockStates and environmentVariables.
* Executes a router block to determine the next execution path.
*
* Process:
* 1. Resolves inputs and gets possible target blocks
* 2. Generates and sends routing prompt to the model
* 3. Processes response to determine chosen path
* 4. Validates and returns routing decision
*
* @param block - The router block to execute
* @param context - Current execution context
* @returns Promise with routing result including chosen path
*/
private async executeRouterBlock(
block: SerializedBlock,
context: ExecutionContext
): Promise<{
content: string
model: string
tokens: {
prompt: number
completion: number
total: number
}
selectedPath: {
blockId: string
blockType: string
blockTitle: string
}
}> {
// Resolve inputs for the router block.
const resolvedInputs = this.resolveInputs(block, context)
const outgoingConnections = this.workflow.connections.filter((conn) => conn.source === block.id)
const targetBlocks = outgoingConnections.map((conn) => {
const targetBlock = this.workflow.blocks.find((b) => b.id === conn.target)
if (!targetBlock) {
throw new Error(`Target block ${conn.target} not found`)
}
return {
id: targetBlock.id,
type: targetBlock.metadata?.type,
title: targetBlock.metadata?.title,
description: targetBlock.metadata?.description,
subBlocks: targetBlock.config.params,
currentState: context.blockStates.get(targetBlock.id),
}
})
const routerConfig = {
prompt: resolvedInputs.prompt,
model: resolvedInputs.model,
apiKey: resolvedInputs.apiKey,
temperature: resolvedInputs.temperature || 0,
}
const model = routerConfig.model || 'gpt-4o'
const providerId = getProviderFromModel(model)
// Generate and send the router prompt.
const response = await executeProviderRequest(providerId, {
model: routerConfig.model,
systemPrompt: generateRouterPrompt(routerConfig.prompt, targetBlocks),
messages: [{ role: 'user', content: routerConfig.prompt }],
temperature: routerConfig.temperature,
apiKey: routerConfig.apiKey,
})
const chosenBlockId = response.content.trim().toLowerCase()
const chosenBlock = targetBlocks.find((b) => b.id === chosenBlockId)
if (!chosenBlock) {
throw new Error(`Invalid routing decision: ${chosenBlockId}`)
}
const tokens = response.tokens || { prompt: 0, completion: 0, total: 0 }
return {
content: resolvedInputs.prompt,
model: response.model,
tokens: {
prompt: tokens.prompt || 0,
completion: tokens.completion || 0,
total: tokens.total || 0,
},
selectedPath: {
blockId: chosenBlock.id,
blockType: chosenBlock.type || 'unknown',
blockTitle: chosenBlock.title || 'Untitled Block',
},
}
}
/**
* Executes an evaluator block which analyzes content against criteria and chooses a path.
*
* Process:
* 1. Resolves inputs and gets possible target blocks
* 2. Generates and sends evaluation prompt to the model
* 3. Processes response to determine chosen path
* 4. Stores evaluation result in context
*
* @param block - The evaluator block to execute
* @param context - Current execution context
* @returns Promise with evaluation result including chosen path
*/
private async executeEvaluatorBlock(
block: SerializedBlock,
context: ExecutionContext
): Promise<{
content: string
model: string
tokens: {
prompt: number
completion: number
total: number
}
selectedPath: {
blockId: string
blockType: string
blockTitle: string
}
}> {
// Resolve inputs for the evaluator block.
const resolvedInputs = this.resolveInputs(block, context)
// Get all possible target blocks from outgoing connections
const outgoingConnections = this.workflow.connections.filter((conn) => conn.source === block.id)
const targetBlocks = outgoingConnections.map((conn) => {
const targetBlock = this.workflow.blocks.find((b) => b.id === conn.target)
if (!targetBlock) {
throw new Error(`Target block ${conn.target} not found`)
}
return {
id: targetBlock.id,
type: targetBlock.metadata?.type,
title: targetBlock.metadata?.title,
description: targetBlock.metadata?.description,
subBlocks: targetBlock.config.params,
currentState: context.blockStates.get(targetBlock.id),
}
})
const evaluatorConfig = {
prompt: resolvedInputs.prompt,
content: resolvedInputs.content,
model: resolvedInputs.model,
apiKey: resolvedInputs.apiKey,
temperature: resolvedInputs.temperature || 0,
}
const model = evaluatorConfig.model || 'gpt-4o'
const providerId = getProviderFromModel(model)
// Generate and execute the evaluator prompt
const response = await executeProviderRequest(providerId, {
model: evaluatorConfig.model,
systemPrompt: generateEvaluatorPrompt(
evaluatorConfig.prompt,
evaluatorConfig.content,
targetBlocks
),
messages: [{ role: 'user', content: evaluatorConfig.prompt }],
temperature: evaluatorConfig.temperature,
apiKey: evaluatorConfig.apiKey,
})
const chosenBlockId = response.content.trim().toLowerCase()
const chosenBlock = targetBlocks.find((b) => b.id === chosenBlockId)
if (!chosenBlock) {
throw new Error(`Invalid evaluation decision: ${chosenBlockId}`)
}
// Store the evaluation result in the context
const tokens = response.tokens || { prompt: 0, completion: 0, total: 0 }
const result = {
content: evaluatorConfig.content,
model: response.model,
tokens: {
prompt: tokens.prompt || 0,
completion: tokens.completion || 0,
total: tokens.total || 0,
},
selectedPath: {
blockId: chosenBlock.id,
blockType: chosenBlock.type || 'unknown',
blockTitle: chosenBlock.title || 'Untitled Block',
},
}
// ADDED: Explicitly store the evaluation decision in the context
context.blockStates.set(block.id, {
response: result,
})
return result
}
/**
* Determines if a block is reachable along the chosen path from a decision block.
*
* Uses breadth-first search to:
* 1. Start from the chosen block
* 2. Follow valid connections
* 3. Skip paths from other routers/evaluators
* 4. Check if target block is reachable
*
* @param blockId - ID of block to check
* @param chosenBlockId - ID of the chosen target block
* @param decisionBlockId - ID of the router/evaluator making the decision
* @returns boolean - Whether the block is reachable
*/
private isInChosenPath(blockId: string, chosenBlockId: string, decisionBlockId: string): boolean {
const visited = new Set<string>()
const queue = [chosenBlockId]
// Add the decision block (router/evaluator) itself as valid
if (blockId === decisionBlockId) {
return true
}
while (queue.length > 0) {
const currentId = queue.shift()!
if (visited.has(currentId)) continue
visited.add(currentId)
// If we found the block we're looking for
if (currentId === blockId) {
return true
}
// Get all outgoing connections from current block
const connections = this.workflow.connections.filter((conn) => conn.source === currentId)
for (const conn of connections) {
// Don't follow connections from other routers/evaluators
const sourceBlock = this.workflow.blocks.find((b) => b.id === conn.source)
if (
sourceBlock?.metadata?.type !== 'router' &&
sourceBlock?.metadata?.type !== 'evaluator'
) {
queue.push(conn.target)
}
}
}
return false
}
/**
* Executes a condition block that evaluates logical conditions and selects a path.
*
* Process:
* 1. Parses and evaluates conditions in order (if/else-if/else)
* 2. Uses source block's output for evaluation context
* 3. Selects matching path based on condition result
* 4. Stores decision in context for downstream execution
*
* @param block - The condition block to execute
* @param context - Current execution context
* @returns Promise with condition result and selected path
*/
private async executeConditionalBlock(
block: SerializedBlock,
context: ExecutionContext
): Promise<{
content: string
condition: boolean
selectedConditionId: string
sourceOutput: BlockOutput
selectedPath: {
blockId: string
blockType: string
blockTitle: string
}
}> {
const conditions = JSON.parse(block.config.params.conditions)
// Identify the source block that feeds into this condition block.
const sourceBlockId = this.workflow.connections.find((conn) => conn.target === block.id)?.source
if (!sourceBlockId) {
throw new Error(`No source block found for condition block ${block.id}`)
}
const sourceOutput = context.blockStates.get(sourceBlockId)
if (!sourceOutput) {
throw new Error(`No output found for source block ${sourceBlockId}`)
}
const outgoingConnections = this.workflow.connections.filter((conn) => conn.source === block.id)
let conditionMet = false
let selectedConnection: { target: string; sourceHandle?: string } | null = null
let selectedCondition: { id: string; title: string; value: string } | null = null
// Evaluate conditions one by one.
for (const condition of conditions) {
try {
// Resolve the condition expression using the current context.
const resolvedCondition = this.resolveInputs(
{
id: block.id,
config: { params: { condition: condition.value }, tool: block.config.tool },
metadata: block.metadata,
position: block.position,
inputs: block.inputs,
outputs: block.outputs,
enabled: block.enabled,
},
context
)
const evalContext = {
...(typeof sourceOutput === 'object' && sourceOutput !== null ? sourceOutput : {}),
agent1: sourceOutput,
}
conditionMet = new Function(
'context',
`with(context) { return ${resolvedCondition.condition} }`
)(evalContext)
// Cast the connection so that TypeScript knows it has a target property.
const connection = outgoingConnections.find(
(conn) => conn.sourceHandle === `condition-${condition.id}`
) as { target: string; sourceHandle?: string } | undefined
if (connection) {
// For if/else-if, require conditionMet to be true.
// For else, unconditionally select it.
if ((condition.title === 'if' || condition.title === 'else if') && conditionMet) {
selectedConnection = connection
selectedCondition = condition
break
} else if (condition.title === 'else') {
selectedConnection = connection
selectedCondition = condition
break
}
}
} catch (error: any) {
console.error(`Failed to evaluate condition: ${error.message}`, {
condition,
error,
})
throw new Error(`Failed to evaluate condition: ${error.message}`)
}
}
if (!selectedConnection || !selectedCondition) {
throw new Error(`No matching path found for condition block ${block.id}`)
}
// Identify the target block based on the selected connection.
const targetBlock = this.workflow.blocks.find((b) => b.id === selectedConnection!.target)
if (!targetBlock) {
throw new Error(`Target block ${selectedConnection!.target} not found`)
}
// Get the raw output from the source block's state
const sourceBlockState = context.blockStates.get(sourceBlockId)
if (!sourceBlockState) {
throw new Error(`No state found for source block ${sourceBlockId}`)
}
// Create the block output with the source output when condition is met
const blockOutput = {
response: {
result: conditionMet ? sourceBlockState : false,
content: `Condition '${selectedCondition.title}' evaluated to ${conditionMet}`,
condition: {
result: conditionMet,
selectedPath: {
blockId: targetBlock.id,
blockType: targetBlock.metadata?.type || '',
blockTitle: targetBlock.metadata?.title || '',
},
selectedConditionId: selectedCondition.id,
},
},
}
// Store the block output in the context
context.blockStates.set(block.id, blockOutput)
return {
content: `Condition '${selectedCondition.title}' chosen`,
condition: conditionMet,
selectedConditionId: selectedCondition.id,
sourceOutput: sourceBlockState,
selectedPath: {
blockId: targetBlock.id,
blockType: targetBlock.metadata?.type || '',
blockTitle: targetBlock.metadata?.title || '',
},
}
}
/**
* Resolves block input values from context and environment.
* Handles template references and variable substitution.
*
* Features:
* - Resolves block references (<blockId.property>)
* - Resolves environment variables ({{ENV_VAR}})
* - Handles special formatting for function blocks
* - Validates references and paths
*
* @param block - Block whose inputs need resolution
* @param context - Current execution context
* @returns Record<string, any> - Resolved input values
*/
private resolveInputs(block: SerializedBlock, context: ExecutionContext): Record<string, any> {
const inputs = { ...block.config.params }
@@ -688,377 +1097,4 @@ export class Executor {
return resolvedInputs
}
/**
* Executes a router block which calculates branching decisions based on a prompt.
*/
private async executeRouterBlock(
block: SerializedBlock,
context: ExecutionContext
): Promise<{
content: string
model: string
tokens: {
prompt: number
completion: number
total: number
}
selectedPath: {
blockId: string
blockType: string
blockTitle: string
}
}> {
// Resolve inputs for the router block.
const resolvedInputs = this.resolveInputs(block, context)
const outgoingConnections = this.workflow.connections.filter((conn) => conn.source === block.id)
const targetBlocks = outgoingConnections.map((conn) => {
const targetBlock = this.workflow.blocks.find((b) => b.id === conn.target)
if (!targetBlock) {
throw new Error(`Target block ${conn.target} not found`)
}
return {
id: targetBlock.id,
type: targetBlock.metadata?.type,
title: targetBlock.metadata?.title,
description: targetBlock.metadata?.description,
subBlocks: targetBlock.config.params,
currentState: context.blockStates.get(targetBlock.id),
}
})
const routerConfig = {
prompt: resolvedInputs.prompt,
model: resolvedInputs.model,
apiKey: resolvedInputs.apiKey,
temperature: resolvedInputs.temperature || 0,
}
const model = routerConfig.model || 'gpt-4o'
const providerId = getProviderFromModel(model)
// Generate and send the router prompt.
const response = await executeProviderRequest(providerId, {
model: routerConfig.model,
systemPrompt: generateRouterPrompt(routerConfig.prompt, targetBlocks),
messages: [{ role: 'user', content: routerConfig.prompt }],
temperature: routerConfig.temperature,
apiKey: routerConfig.apiKey,
})
const chosenBlockId = response.content.trim().toLowerCase()
const chosenBlock = targetBlocks.find((b) => b.id === chosenBlockId)
if (!chosenBlock) {
throw new Error(`Invalid routing decision: ${chosenBlockId}`)
}
const tokens = response.tokens || { prompt: 0, completion: 0, total: 0 }
return {
content: resolvedInputs.prompt,
model: response.model,
tokens: {
prompt: tokens.prompt || 0,
completion: tokens.completion || 0,
total: tokens.total || 0,
},
selectedPath: {
blockId: chosenBlock.id,
blockType: chosenBlock.type || 'unknown',
blockTitle: chosenBlock.title || 'Untitled Block',
},
}
}
/**
* Executes a router block which calculates branching decisions based on a prompt.
*/
private async executeEvaluatorBlock(
block: SerializedBlock,
context: ExecutionContext
): Promise<{
content: string
model: string
tokens: {
prompt: number
completion: number
total: number
}
selectedPath: {
blockId: string
blockType: string
blockTitle: string
}
}> {
// Resolve inputs for the evaluator block.
console.log('Evaluator: Resolving inputs for the evaluator block.')
const resolvedInputs = this.resolveInputs(block, context)
console.log('Evaluator: Resolved inputs:', resolvedInputs)
// Get all possible target blocks from outgoing connections
const outgoingConnections = this.workflow.connections.filter((conn) => conn.source === block.id)
console.log('Evaluator: Outgoing connections:', outgoingConnections)
const targetBlocks = outgoingConnections.map((conn) => {
const targetBlock = this.workflow.blocks.find((b) => b.id === conn.target)
if (!targetBlock) {
throw new Error(`Target block ${conn.target} not found`)
}
console.log('Evaluator: Found target block:', targetBlock)
return {
id: targetBlock.id,
type: targetBlock.metadata?.type,
title: targetBlock.metadata?.title,
description: targetBlock.metadata?.description,
subBlocks: targetBlock.config.params,
currentState: context.blockStates.get(targetBlock.id),
}
})
console.log('Evaluator: Mapped target blocks:', targetBlocks)
const evaluatorConfig = {
prompt: resolvedInputs.prompt,
content: resolvedInputs.content,
model: resolvedInputs.model,
apiKey: resolvedInputs.apiKey,
temperature: resolvedInputs.temperature || 0,
}
const model = evaluatorConfig.model || 'gpt-4o'
const providerId = getProviderFromModel(model)
// Generate and execute the evaluator prompt
console.log('Evaluator: Sending request with config:', evaluatorConfig)
const response = await executeProviderRequest(providerId, {
model: evaluatorConfig.model,
systemPrompt: generateEvaluatorPrompt(
evaluatorConfig.prompt,
evaluatorConfig.content,
targetBlocks
),
messages: [{ role: 'user', content: evaluatorConfig.prompt }],
temperature: evaluatorConfig.temperature,
apiKey: evaluatorConfig.apiKey,
})
console.log('Evaluator: Raw response:', response)
const chosenBlockId = response.content.trim().toLowerCase()
console.log('Evaluator: Chosen block ID:', chosenBlockId)
const chosenBlock = targetBlocks.find((b) => b.id === chosenBlockId)
if (!chosenBlock) {
throw new Error(`Invalid evaluation decision: ${chosenBlockId}`)
}
// Store the evaluation result in the context
const tokens = response.tokens || { prompt: 0, completion: 0, total: 0 }
const result = {
content: evaluatorConfig.prompt,
model: response.model,
tokens: {
prompt: tokens.prompt || 0,
completion: tokens.completion || 0,
total: tokens.total || 0,
},
selectedPath: {
blockId: chosenBlock.id,
blockType: chosenBlock.type || 'unknown',
blockTitle: chosenBlock.title || 'Untitled Block',
},
}
// ADDED: Explicitly store the evaluation decision in the context
context.blockStates.set(block.id, {
response: result,
})
return result
}
/**
* Determines whether a block is reachable along the chosen router path.
*
* This uses a breadth-first search starting from the chosen block id.
*/
private isInChosenPath(blockId: string, chosenBlockId: string, decisionBlockId: string): boolean {
const visited = new Set<string>()
const queue = [chosenBlockId]
// Add the decision block (router/evaluator) itself as valid
if (blockId === decisionBlockId) {
return true
}
while (queue.length > 0) {
const currentId = queue.shift()!
if (visited.has(currentId)) continue
visited.add(currentId)
// If we found the block we're looking for
if (currentId === blockId) {
return true
}
// Get all outgoing connections from current block
const connections = this.workflow.connections.filter((conn) => conn.source === currentId)
for (const conn of connections) {
// Don't follow connections from other routers/evaluators
const sourceBlock = this.workflow.blocks.find((b) => b.id === conn.source)
if (
sourceBlock?.metadata?.type !== 'router' &&
sourceBlock?.metadata?.type !== 'evaluator'
) {
queue.push(conn.target)
}
}
}
return false
}
/**
* Executes a condition block that evaluates a set of conditions (if/else-if/else).
*
* The block:
* - Parses its conditions.
* - Uses the source block's output to evaluate each condition.
* - Selects the branch matching the evaluation (via sourceHandle in the connection).
* - Returns an output that includes the boolean result and the selected condition's ID.
*/
private async executeConditionalBlock(
block: SerializedBlock,
context: ExecutionContext
): Promise<{
content: string
condition: boolean
selectedConditionId: string
sourceOutput: BlockOutput
selectedPath: {
blockId: string
blockType: string
blockTitle: string
}
}> {
const conditions = JSON.parse(block.config.params.conditions)
console.log('Parsed conditions:', conditions)
// Identify the source block that feeds into this condition block.
const sourceBlockId = this.workflow.connections.find((conn) => conn.target === block.id)?.source
if (!sourceBlockId) {
throw new Error(`No source block found for condition block ${block.id}`)
}
const sourceOutput = context.blockStates.get(sourceBlockId)
if (!sourceOutput) {
throw new Error(`No output found for source block ${sourceBlockId}`)
}
console.log('Source block output:', sourceOutput)
const outgoingConnections = this.workflow.connections.filter((conn) => conn.source === block.id)
console.log('Outgoing connections:', outgoingConnections)
let conditionMet = false
let selectedConnection: { target: string; sourceHandle?: string } | null = null
let selectedCondition: { id: string; title: string; value: string } | null = null
// Evaluate conditions one by one.
for (const condition of conditions) {
try {
// Resolve the condition expression using the current context.
const resolvedCondition = this.resolveInputs(
{
id: block.id,
config: { params: { condition: condition.value }, tool: block.config.tool },
metadata: block.metadata,
position: block.position,
inputs: block.inputs,
outputs: block.outputs,
enabled: block.enabled,
},
context
)
const evalContext = {
...(typeof sourceOutput === 'object' && sourceOutput !== null ? sourceOutput : {}),
agent1: sourceOutput,
}
conditionMet = new Function(
'context',
`with(context) { return ${resolvedCondition.condition} }`
)(evalContext)
// Cast the connection so that TypeScript knows it has a target property.
const connection = outgoingConnections.find(
(conn) => conn.sourceHandle === `condition-${condition.id}`
) as { target: string; sourceHandle?: string } | undefined
if (connection) {
// For if/else-if, require conditionMet to be true.
// For else, unconditionally select it.
if ((condition.title === 'if' || condition.title === 'else if') && conditionMet) {
selectedConnection = connection
selectedCondition = condition
break
} else if (condition.title === 'else') {
selectedConnection = connection
selectedCondition = condition
break
}
}
} catch (error: any) {
console.error(`Failed to evaluate condition: ${error.message}`, {
condition,
error,
})
throw new Error(`Failed to evaluate condition: ${error.message}`)
}
}
if (!selectedConnection || !selectedCondition) {
throw new Error(`No matching path found for condition block ${block.id}`)
}
// Identify the target block based on the selected connection.
const targetBlock = this.workflow.blocks.find((b) => b.id === selectedConnection!.target)
if (!targetBlock) {
throw new Error(`Target block ${selectedConnection!.target} not found`)
}
// Get the raw output from the source block's state
const sourceBlockState = context.blockStates.get(sourceBlockId)
if (!sourceBlockState) {
throw new Error(`No state found for source block ${sourceBlockId}`)
}
// Create the block output with the source output when condition is met
const blockOutput = {
response: {
result: conditionMet ? sourceBlockState : false,
content: `Condition '${selectedCondition.title}' evaluated to ${conditionMet}`,
condition: {
result: conditionMet,
selectedPath: {
blockId: targetBlock.id,
blockType: targetBlock.metadata?.type || '',
blockTitle: targetBlock.metadata?.title || '',
},
selectedConditionId: selectedCondition.id,
},
},
}
// Store the block output in the context
context.blockStates.set(block.id, blockOutput)
return {
content: `Condition '${selectedCondition.title}' chosen`,
condition: conditionMet,
selectedConditionId: selectedCondition.id,
sourceOutput: sourceBlockState,
selectedPath: {
blockId: targetBlock.id,
blockType: targetBlock.metadata?.type || '',
blockTitle: targetBlock.metadata?.title || '',
},
}
}
}