Skip to content

Commit eb6bdf0

Browse files
icecrasher321waleedlatif1
authored andcommitted
improvement(func-exec): normalize inputs to match schema (#4473)
1 parent 0223509 commit eb6bdf0

18 files changed

Lines changed: 325 additions & 50 deletions

File tree

apps/sim/executor/execution/block-executor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createLogger, type Logger } from '@sim/logger'
22
import { toError } from '@sim/utils/errors'
33
import { redactApiKeys } from '@/lib/core/security/redaction'
4+
import { normalizeStringArray } from '@/lib/core/utils/arrays'
45
import { getBaseUrl } from '@/lib/core/utils/urls'
56
import {
67
containsUserFileWithMetadata,
@@ -164,7 +165,7 @@ export class BlockExecutor {
164165
block,
165166
streamingExec,
166167
resolvedInputs,
167-
ctx.selectedOutputs ?? []
168+
normalizeStringArray(ctx.selectedOutputs)
168169
)
169170
}
170171

apps/sim/executor/execution/executor.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { createLogger, type Logger } from '@sim/logger'
2+
import { normalizeStringArray } from '@/lib/core/utils/arrays'
3+
import { normalizeStringRecord, normalizeWorkflowVariables } from '@/lib/core/utils/records'
24
import { StartBlockPath } from '@/lib/workflows/triggers/triggers'
35
import type { DAG } from '@/executor/dag/builder'
46
import { DAGBuilder } from '@/executor/dag/builder'
@@ -56,9 +58,9 @@ export class DAGExecutor {
5658

5759
constructor(options: DAGExecutorOptions) {
5860
this.workflow = options.workflow
59-
this.environmentVariables = options.envVarValues ?? {}
61+
this.environmentVariables = normalizeStringRecord(options.envVarValues)
6062
this.workflowInput = options.workflowInput ?? {}
61-
this.workflowVariables = options.workflowVariables ?? {}
63+
this.workflowVariables = normalizeWorkflowVariables(options.workflowVariables)
6264
this.contextExtensions = options.contextExtensions ?? {}
6365
this.dagBuilder = new DAGBuilder()
6466
this.execLogger = logger.withMetadata({
@@ -325,7 +327,7 @@ export class DAGExecutor {
325327
: new Set(),
326328
workflow: this.workflow,
327329
stream: this.contextExtensions.stream ?? false,
328-
selectedOutputs: this.contextExtensions.selectedOutputs ?? [],
330+
selectedOutputs: normalizeStringArray(this.contextExtensions.selectedOutputs),
329331
edges: this.contextExtensions.edges ?? [],
330332
onStream: this.contextExtensions.onStream,
331333
onBlockStart: this.contextExtensions.onBlockStart,

apps/sim/executor/execution/snapshot-serializer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ export function serializePauseSnapshot(
119119
executionMetadata,
120120
context.workflow,
121121
{},
122-
context.workflowVariables ?? {},
123-
context.selectedOutputs ?? [],
122+
context.workflowVariables,
123+
context.selectedOutputs,
124124
state
125125
)
126126

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { ExecutionSnapshot } from '@/executor/execution/snapshot'
3+
import type { ExecutionMetadata } from '@/executor/execution/types'
4+
5+
const metadata: ExecutionMetadata = {
6+
requestId: 'request-1',
7+
executionId: 'execution-1',
8+
workflowId: 'workflow-1',
9+
workspaceId: 'workspace-1',
10+
userId: 'user-1',
11+
triggerType: 'manual',
12+
startTime: '2026-05-06T00:00:00.000Z',
13+
}
14+
15+
describe('ExecutionSnapshot', () => {
16+
it('normalizes untyped persisted execution state at construction', () => {
17+
const variable = { id: 'var-1', name: 'brand', type: 'plain', value: 'myfitness' }
18+
19+
const snapshot = new ExecutionSnapshot(
20+
metadata,
21+
{ blocks: [] },
22+
{},
23+
[variable],
24+
['agent.content', 123, 'function.result']
25+
)
26+
27+
expect(snapshot.workflowVariables).toEqual({ 'var-1': variable })
28+
expect(snapshot.selectedOutputs).toEqual(['agent.content', 'function.result'])
29+
})
30+
})

apps/sim/executor/execution/snapshot.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
1+
import { normalizeStringArray } from '@/lib/core/utils/arrays'
2+
import { normalizeWorkflowVariables } from '@/lib/core/utils/records'
13
import type { ExecutionMetadata, SerializableExecutionState } from '@/executor/execution/types'
24

35
export class ExecutionSnapshot {
6+
public readonly metadata: ExecutionMetadata
7+
public readonly workflow: any
8+
public readonly input: any
9+
public readonly workflowVariables: Record<string, any>
10+
public readonly selectedOutputs: string[]
11+
public readonly state?: SerializableExecutionState
12+
413
constructor(
5-
public readonly metadata: ExecutionMetadata,
6-
public readonly workflow: any,
7-
public readonly input: any,
8-
public readonly workflowVariables: Record<string, any>,
9-
public readonly selectedOutputs: string[] = [],
10-
public readonly state?: SerializableExecutionState
11-
) {}
14+
metadata: ExecutionMetadata,
15+
workflow: any,
16+
input: any,
17+
workflowVariables: unknown,
18+
selectedOutputs: unknown = [],
19+
state?: SerializableExecutionState
20+
) {
21+
this.metadata = metadata
22+
this.workflow = workflow
23+
this.input = input
24+
this.workflowVariables = normalizeWorkflowVariables(workflowVariables)
25+
this.selectedOutputs = normalizeStringArray(selectedOutputs)
26+
this.state = state
27+
}
1228

1329
toJSON(): string {
1430
return JSON.stringify({

apps/sim/executor/handlers/agent/agent-handler.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createLogger } from '@sim/logger'
44
import { toError } from '@sim/utils/errors'
55
import { sleep } from '@sim/utils/helpers'
66
import { and, eq, inArray, isNull } from 'drizzle-orm'
7+
import { normalizeStringRecord, normalizeWorkflowVariables } from '@/lib/core/utils/records'
78
import { createMcpToolId } from '@/lib/mcp/utils'
89
import { getCustomToolById } from '@/lib/workflows/custom-tools/operations'
910
import { getAllBlocks } from '@/blocks'
@@ -815,8 +816,8 @@ export class AgentBlockHandler implements BlockHandler {
815816
userId: ctx.userId,
816817
stream: streaming,
817818
messages: messages?.map(({ executionId, ...msg }) => msg),
818-
environmentVariables: ctx.environmentVariables || {},
819-
workflowVariables: ctx.workflowVariables || {},
819+
environmentVariables: normalizeStringRecord(ctx.environmentVariables),
820+
workflowVariables: normalizeWorkflowVariables(ctx.workflowVariables),
820821
blockData,
821822
blockNameMapping,
822823
reasoningEffort: inputs.reasoningEffort,
@@ -885,8 +886,8 @@ export class AgentBlockHandler implements BlockHandler {
885886
userId: ctx.userId,
886887
stream: providerRequest.stream,
887888
messages: 'messages' in providerRequest ? providerRequest.messages : undefined,
888-
environmentVariables: ctx.environmentVariables || {},
889-
workflowVariables: ctx.workflowVariables || {},
889+
environmentVariables: normalizeStringRecord(ctx.environmentVariables),
890+
workflowVariables: normalizeWorkflowVariables(ctx.workflowVariables),
890891
blockData,
891892
blockNameMapping,
892893
isDeployedContext: ctx.isDeployedContext,

apps/sim/executor/handlers/condition/condition-handler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createLogger } from '@sim/logger'
2+
import { normalizeStringRecord, normalizeWorkflowVariables } from '@/lib/core/utils/records'
23
import type { BlockOutput } from '@/blocks/types'
34
import { BlockType, CONDITION, DEFAULTS, EDGE } from '@/executor/constants'
45
import type { BlockHandler, ExecutionContext } from '@/executor/types'
@@ -40,8 +41,8 @@ export async function evaluateConditionExpression(
4041
{
4142
code,
4243
timeout: CONDITION_TIMEOUT_MS,
43-
envVars: ctx.environmentVariables || {},
44-
workflowVariables: ctx.workflowVariables || {},
44+
envVars: normalizeStringRecord(ctx.environmentVariables),
45+
workflowVariables: normalizeWorkflowVariables(ctx.workflowVariables),
4546
blockData,
4647
blockNameMapping,
4748
blockOutputSchemas,

apps/sim/executor/handlers/function/function-handler.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,28 @@ describe('FunctionBlockHandler', () => {
196196
)
197197
})
198198

199+
it('should normalize malformed execution context records before calling function_execute', async () => {
200+
const legacyVariable = { id: 'var-1', name: 'brand', type: 'plain', value: 'myfitness' }
201+
mockContext.workflowVariables = [legacyVariable] as unknown as Record<string, any>
202+
mockContext.environmentVariables = ['invalid-env'] as unknown as Record<string, string>
203+
204+
await handler.execute(mockContext, mockBlock, {
205+
code: 'return "myfitness"',
206+
[FUNCTION_BLOCK_CONTEXT_VARS_KEY]: ['invalid-context'],
207+
})
208+
209+
expect(mockExecuteTool).toHaveBeenCalledWith(
210+
'function_execute',
211+
expect.objectContaining({
212+
envVars: {},
213+
workflowVariables: { 'var-1': legacyVariable },
214+
contextVariables: {},
215+
}),
216+
false,
217+
mockContext
218+
)
219+
})
220+
199221
it('should handle tool error with no specific message', async () => {
200222
const inputs = { code: 'some code' }
201223
const errorResult = { success: false }

apps/sim/executor/handlers/function/function-handler.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import {
2+
normalizeRecord,
3+
normalizeStringRecord,
4+
normalizeWorkflowVariables,
5+
} from '@/lib/core/utils/records'
16
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/execution/constants'
27
import { DEFAULT_CODE_LANGUAGE } from '@/lib/execution/languages'
38
import { BlockType } from '@/executor/constants'
@@ -26,17 +31,16 @@ export class FunctionBlockHandler implements BlockHandler {
2631

2732
const { blockData, blockNameMapping, blockOutputSchemas } = collectBlockData(ctx)
2833

29-
const contextVariables =
30-
(inputs[FUNCTION_BLOCK_CONTEXT_VARS_KEY] as Record<string, unknown> | undefined) ?? {}
34+
const contextVariables = normalizeRecord(inputs[FUNCTION_BLOCK_CONTEXT_VARS_KEY])
3135

3236
const result = await executeTool(
3337
'function_execute',
3438
{
3539
code: codeContent,
3640
language: inputs.language || DEFAULT_CODE_LANGUAGE,
3741
timeout: inputs.timeout || DEFAULT_EXECUTION_TIMEOUT_MS,
38-
envVars: ctx.environmentVariables || {},
39-
workflowVariables: ctx.workflowVariables || {},
42+
envVars: normalizeStringRecord(ctx.environmentVariables),
43+
workflowVariables: normalizeWorkflowVariables(ctx.workflowVariables),
4044
blockData,
4145
blockNameMapping,
4246
blockOutputSchemas,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { normalizeStringArray } from '@/lib/core/utils/arrays'
3+
4+
describe('array normalization utilities', () => {
5+
it('normalizes string arrays loaded from untyped state', () => {
6+
expect(normalizeStringArray(['output-1', 2, 'output-2', null])).toEqual([
7+
'output-1',
8+
'output-2',
9+
])
10+
expect(normalizeStringArray('output-1')).toEqual([])
11+
expect(normalizeStringArray(undefined)).toEqual([])
12+
})
13+
})

0 commit comments

Comments
 (0)