Feat/api input (#89)

* feat(api-input): correctly passing in body into executor; need to resolve tag value in blocks following starter

* feat(api-input): input is now resolved

* feat(api-input): cleaned up logs and added comments
This commit is contained in:
Emir Karabeg
2025-02-27 14:01:42 -08:00
committed by GitHub
parent 5c11e9da16
commit 18c16c217c
6 changed files with 89 additions and 14 deletions
+1 -1
View File
@@ -113,7 +113,7 @@ async function executeWorkflow(workflow: any, input?: any) {
// Serialize and execute the workflow
const serializedWorkflow = new Serializer().serializeWorkflow(mergedStates, edges, loops)
const executor = new Executor(serializedWorkflow, currentBlockStates, decryptedEnvVars)
const executor = new Executor(serializedWorkflow, currentBlockStates, decryptedEnvVars, input)
const result = await executor.execute(workflowId)
// Log each execution step
+2 -5
View File
@@ -131,15 +131,12 @@ export const StarterBlock: BlockConfig = {
access: [],
},
inputs: {
code: { type: 'string', required: true },
executionMode: { type: 'string', required: true },
input: { type: 'json', required: false },
},
outputs: {
response: {
type: {
result: 'any',
stdout: 'string',
executionTime: 'number',
input: 'any',
},
},
},
+4 -1
View File
@@ -103,12 +103,15 @@ export class AgentBlockHandler implements BlockHandler {
.filter((t): t is NonNullable<typeof t> => t !== null)
: []
// Ensure context is properly formatted for the provider
const response = await executeProviderRequest(providerId, {
model,
systemPrompt: inputs.systemPrompt,
context: Array.isArray(inputs.context)
? JSON.stringify(inputs.context, null, 2)
: inputs.context,
: typeof inputs.context === 'string'
? inputs.context
: JSON.stringify(inputs.context, null, 2),
tools: formattedTools.length > 0 ? formattedTools : undefined,
temperature: inputs.temperature,
maxTokens: inputs.maxTokens,
+25 -2
View File
@@ -28,13 +28,16 @@ export class Executor {
private loopManager: LoopManager
private pathTracker: PathTracker
private blockHandlers: BlockHandler[]
private workflowInput: any
constructor(
private workflow: SerializedWorkflow,
private initialBlockStates: Record<string, BlockOutput> = {},
private environmentVariables: Record<string, string> = {}
private environmentVariables: Record<string, string> = {},
workflowInput?: any
) {
this.validateWorkflow()
this.workflowInput = workflowInput || {}
this.resolver = new InputResolver(workflow, environmentVariables)
this.loopManager = new LoopManager(workflow.loops || {})
@@ -211,11 +214,22 @@ export class Executor {
const starterBlock = this.workflow.blocks.find((block) => block.metadata?.id === 'starter')
if (starterBlock) {
// Initialize the starter block with the workflow input
const starterOutput = {
response: {
type: {
input: this.workflowInput,
},
},
}
context.blockStates.set(starterBlock.id, {
output: { response: { result: true } },
output: starterOutput,
executed: true,
executionTime: 0,
})
// Mark the starter block as executed and add its connections to the active path
context.executedBlocks.add(starterBlock.id)
const connectedToStarter = this.workflow.connections
@@ -371,6 +385,15 @@ export class Executor {
throw new Error(`Block ${blockId} not found`)
}
// Special case for starter block - it's already been initialized in createExecutionContext
// This ensures we don't re-execute the starter block and just return its existing state
if (block.metadata?.id === 'starter') {
const starterState = context.blockStates.get(blockId)
if (starterState) {
return starterState.output as NormalizedBlockOutput
}
}
const blockLog = this.createBlockLog(block)
const addConsole = useConsoleStore.getState().addConsole
+56 -2
View File
@@ -14,12 +14,27 @@ export class InputResolver {
) {
// Create maps for efficient lookups
this.blockById = new Map(workflow.blocks.map((block) => [block.id, block]))
// Initialize the normalized name map
this.blockByNormalizedName = new Map(
workflow.blocks.map((block) => [
block.metadata?.name ? this.normalizeBlockName(block.metadata.name) : block.id,
block,
])
)
// Add special handling for the starter block - allow referencing it as "start"
const starterBlock = workflow.blocks.find((block) => block.metadata?.id === 'starter')
if (starterBlock) {
this.blockByNormalizedName.set('start', starterBlock)
// Also add the normalized actual name if it exists
if (starterBlock.metadata?.name) {
this.blockByNormalizedName.set(
this.normalizeBlockName(starterBlock.metadata.name),
starterBlock
)
}
}
}
/**
@@ -99,15 +114,53 @@ export class InputResolver {
const path = match.slice(1, -1)
const [blockRef, ...pathParts] = path.split('.')
let sourceBlock = this.blockById.get(blockRef)
// Special case for "start" references
// This allows users to reference the starter block using <start.response.type.input>
// regardless of the actual name of the starter block
if (blockRef.toLowerCase() === 'start') {
// Find the starter block
const starterBlock = this.workflow.blocks.find((block) => block.metadata?.id === 'starter')
if (starterBlock) {
const blockState = context.blockStates.get(starterBlock.id)
if (blockState) {
// Navigate through the path parts
let replacementValue: any = blockState.output
for (const part of pathParts) {
if (!replacementValue || typeof replacementValue !== 'object') {
throw new Error(`Invalid path "${part}" in "${path}" for starter block.`)
}
replacementValue = replacementValue[part]
if (replacementValue === undefined) {
throw new Error(`No value found at path "${path}" in starter block.`)
}
}
// Format the value
const formattedValue =
typeof replacementValue === 'object'
? JSON.stringify(replacementValue)
: String(replacementValue)
resolvedValue = resolvedValue.replace(match, formattedValue)
continue
}
}
}
// Standard block reference resolution
let sourceBlock = this.blockById.get(blockRef)
if (!sourceBlock) {
const normalizedRef = this.normalizeBlockName(blockRef)
sourceBlock = this.blockByNormalizedName.get(normalizedRef)
}
if (!sourceBlock) {
throw new Error(`Block reference "${blockRef}" was not found.`)
// Provide a more helpful error message with available block names
const availableBlocks = Array.from(this.blockByNormalizedName.keys()).join(', ')
throw new Error(
`Block reference "${blockRef}" was not found. Available blocks: ${availableBlocks}. ` +
`For the starter block, try using "start" or the exact block name.`
)
}
if (sourceBlock.enabled === false) {
@@ -156,6 +209,7 @@ export class InputResolver {
`Invalid path "${part}" in "${path}" for block "${currentBlock.metadata?.name || currentBlock.id}".`
)
}
replacementValue = replacementValue[part]
if (replacementValue === undefined) {
+1 -3
View File
@@ -176,9 +176,7 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
outputs: {
response: {
type: {
result: 'any',
stdout: 'string',
executionTime: 'number',
input: 'any',
},
},
},