Compare commits

...

5 Commits

Author SHA1 Message Date
Vikhyath Mondreti
1a90c04e1c fix 2026-01-25 23:07:45 -08:00
Vikhyath Mondreti
5b3cc2ba0d update type guard: 2026-01-25 23:03:55 -08:00
Vikhyath Mondreti
7ba07bc428 fix output condition logic 2026-01-25 22:56:13 -08:00
Vikhyath Mondreti
0c69f8b061 fix trigger input format version 2026-01-25 22:44:09 -08:00
Vikhyath Mondreti
57e6203625 fix(multi-trigger): resolution paths for triggers 2026-01-25 22:32:51 -08:00
5 changed files with 93 additions and 37 deletions

View File

@@ -581,6 +581,18 @@ export const GmailV2Block: BlockConfig<GmailToolResponse> = {
results: { type: 'json', description: 'Search/read summary results' }, results: { type: 'json', description: 'Search/read summary results' },
attachments: { type: 'json', description: 'Downloaded attachments (if enabled)' }, attachments: { type: 'json', description: 'Downloaded attachments (if enabled)' },
// Draft-specific outputs
draftId: {
type: 'string',
description: 'Draft ID',
condition: { field: 'operation', value: 'draft_gmail' },
},
messageId: {
type: 'string',
description: 'Gmail message ID for the draft',
condition: { field: 'operation', value: 'draft_gmail' },
},
// Trigger outputs (unchanged) // Trigger outputs (unchanged)
email_id: { type: 'string', description: 'Gmail message ID' }, email_id: { type: 'string', description: 'Gmail message ID' },
thread_id: { type: 'string', description: 'Gmail thread ID' }, thread_id: { type: 'string', description: 'Gmail thread ID' },

View File

@@ -1,7 +1,9 @@
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { normalizeName } from '@/executor/constants' import { normalizeName } from '@/executor/constants'
import type { ExecutionContext } from '@/executor/types' import type { ExecutionContext } from '@/executor/types'
import type { OutputSchema } from '@/executor/utils/block-reference' import type { OutputSchema } from '@/executor/utils/block-reference'
import type { SerializedBlock } from '@/serializer/types'
import type { ToolConfig } from '@/tools/types'
import { getTool } from '@/tools/utils'
export interface BlockDataCollection { export interface BlockDataCollection {
blockData: Record<string, unknown> blockData: Record<string, unknown>
@@ -9,6 +11,32 @@ export interface BlockDataCollection {
blockOutputSchemas: Record<string, OutputSchema> blockOutputSchemas: Record<string, OutputSchema>
} }
export function getBlockSchema(
block: SerializedBlock,
toolConfig?: ToolConfig
): OutputSchema | undefined {
const isTrigger =
block.metadata?.category === 'triggers' ||
(block.config?.params as Record<string, unknown> | undefined)?.triggerMode === true
// Triggers use saved outputs (defines the trigger payload schema)
if (isTrigger && block.outputs && Object.keys(block.outputs).length > 0) {
return block.outputs as OutputSchema
}
// When a tool is selected, tool outputs are the source of truth
if (toolConfig?.outputs && Object.keys(toolConfig.outputs).length > 0) {
return toolConfig.outputs as OutputSchema
}
// Fallback to saved outputs for blocks without tools
if (block.outputs && Object.keys(block.outputs).length > 0) {
return block.outputs as OutputSchema
}
return undefined
}
export function collectBlockData(ctx: ExecutionContext): BlockDataCollection { export function collectBlockData(ctx: ExecutionContext): BlockDataCollection {
const blockData: Record<string, unknown> = {} const blockData: Record<string, unknown> = {}
const blockNameMapping: Record<string, string> = {} const blockNameMapping: Record<string, string> = {}
@@ -18,24 +46,21 @@ export function collectBlockData(ctx: ExecutionContext): BlockDataCollection {
if (state.output !== undefined) { if (state.output !== undefined) {
blockData[id] = state.output blockData[id] = state.output
} }
}
const workflowBlock = ctx.workflow?.blocks?.find((b) => b.id === id) const workflowBlocks = ctx.workflow?.blocks ?? []
if (!workflowBlock) continue for (const block of workflowBlocks) {
const id = block.id
if (workflowBlock.metadata?.name) { if (block.metadata?.name) {
blockNameMapping[normalizeName(workflowBlock.metadata.name)] = id blockNameMapping[normalizeName(block.metadata.name)] = id
} }
const blockType = workflowBlock.metadata?.id const toolId = block.config?.tool
if (blockType) { const toolConfig = toolId ? getTool(toolId) : undefined
const params = workflowBlock.config?.params as Record<string, unknown> | undefined const schema = getBlockSchema(block, toolConfig)
const subBlocks = params if (schema && Object.keys(schema).length > 0) {
? Object.fromEntries(Object.entries(params).map(([k, v]) => [k, { value: v }])) blockOutputSchemas[id] = schema
: undefined
const schema = getBlockOutputs(blockType, subBlocks)
if (schema && Object.keys(schema).length > 0) {
blockOutputSchemas[id] = schema
}
} }
} }

View File

@@ -378,8 +378,30 @@ function buildManualTriggerOutput(
return mergeFilesIntoOutput(output, workflowInput) return mergeFilesIntoOutput(output, workflowInput)
} }
function buildIntegrationTriggerOutput(workflowInput: unknown): NormalizedBlockOutput { function buildIntegrationTriggerOutput(
return isPlainObject(workflowInput) ? (workflowInput as NormalizedBlockOutput) : {} workflowInput: unknown,
structuredInput: Record<string, unknown>,
hasStructured: boolean
): NormalizedBlockOutput {
const output: NormalizedBlockOutput = {}
if (hasStructured) {
for (const [key, value] of Object.entries(structuredInput)) {
output[key] = value
}
}
if (isPlainObject(workflowInput)) {
for (const [key, value] of Object.entries(workflowInput)) {
if (value !== undefined && value !== null) {
output[key] = value
} else if (!Object.hasOwn(output, key)) {
output[key] = value
}
}
}
return mergeFilesIntoOutput(output, workflowInput)
} }
function extractSubBlocks(block: SerializedBlock): Record<string, unknown> | undefined { function extractSubBlocks(block: SerializedBlock): Record<string, unknown> | undefined {
@@ -428,7 +450,7 @@ export function buildStartBlockOutput(options: StartBlockOutputOptions): Normali
return buildManualTriggerOutput(finalInput, workflowInput) return buildManualTriggerOutput(finalInput, workflowInput)
case StartBlockPath.EXTERNAL_TRIGGER: case StartBlockPath.EXTERNAL_TRIGGER:
return buildIntegrationTriggerOutput(workflowInput) return buildIntegrationTriggerOutput(workflowInput, structuredInput, hasStructured)
case StartBlockPath.LEGACY_STARTER: case StartBlockPath.LEGACY_STARTER:
return buildLegacyStarterOutput( return buildLegacyStarterOutput(

View File

@@ -1,10 +1,10 @@
import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { import {
isReference, isReference,
normalizeName, normalizeName,
parseReferencePath, parseReferencePath,
SPECIAL_REFERENCE_PREFIXES, SPECIAL_REFERENCE_PREFIXES,
} from '@/executor/constants' } from '@/executor/constants'
import { getBlockSchema } from '@/executor/utils/block-data'
import { import {
InvalidFieldError, InvalidFieldError,
type OutputSchema, type OutputSchema,
@@ -67,15 +67,9 @@ export class BlockResolver implements Resolver {
blockData[blockId] = output blockData[blockId] = output
} }
const blockType = block.metadata?.id
const params = block.config?.params as Record<string, unknown> | undefined
const subBlocks = params
? Object.fromEntries(Object.entries(params).map(([k, v]) => [k, { value: v }]))
: undefined
const toolId = block.config?.tool const toolId = block.config?.tool
const toolConfig = toolId ? getTool(toolId) : undefined const toolConfig = toolId ? getTool(toolId) : undefined
const outputSchema = const outputSchema = getBlockSchema(block, toolConfig)
toolConfig?.outputs ?? (blockType ? getBlockOutputs(blockType, subBlocks) : block.outputs)
if (outputSchema && Object.keys(outputSchema).length > 0) { if (outputSchema && Object.keys(outputSchema).length > 0) {
blockOutputSchemas[blockId] = outputSchema blockOutputSchemas[blockId] = outputSchema

View File

@@ -618,13 +618,6 @@ export function getToolOutputs(
} }
} }
/**
* Generates output paths for a tool-based block.
*
* @param blockConfig - The block configuration containing tools config
* @param subBlocks - SubBlock values for tool selection and condition evaluation
* @returns Array of output paths for the tool, or empty array on error
*/
export function getToolOutputPaths( export function getToolOutputPaths(
blockConfig: BlockConfig, blockConfig: BlockConfig,
subBlocks?: Record<string, SubBlockWithValue> subBlocks?: Record<string, SubBlockWithValue>
@@ -634,12 +627,22 @@ export function getToolOutputPaths(
if (!outputs || Object.keys(outputs).length === 0) return [] if (!outputs || Object.keys(outputs).length === 0) return []
if (subBlocks && blockConfig.outputs) { if (subBlocks && blockConfig.outputs) {
const filteredBlockOutputs = filterOutputsByCondition(blockConfig.outputs, subBlocks)
const allowedKeys = new Set(Object.keys(filteredBlockOutputs))
const filteredOutputs: Record<string, any> = {} const filteredOutputs: Record<string, any> = {}
for (const [key, value] of Object.entries(outputs)) { for (const [key, value] of Object.entries(outputs)) {
if (allowedKeys.has(key)) { const blockOutput = blockConfig.outputs[key]
if (!blockOutput || typeof blockOutput !== 'object') {
filteredOutputs[key] = value
continue
}
const condition = 'condition' in blockOutput ? blockOutput.condition : undefined
if (condition) {
if (evaluateOutputCondition(condition, subBlocks)) {
filteredOutputs[key] = value
}
} else {
filteredOutputs[key] = value filteredOutputs[key] = value
} }
} }