Files
simstudioai--sim/apps/sim/executor/variables/resolver.test.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

1322 lines
43 KiB
TypeScript

/**
* @vitest-environment node
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
LARGE_ARRAY_MANIFEST_VERSION,
type LargeArrayManifest,
} from '@/lib/execution/payloads/large-array-manifest-metadata'
import { BlockType } from '@/executor/constants'
import { ExecutionState } from '@/executor/execution/state'
import type { ExecutionContext } from '@/executor/types'
import { VariableResolver } from '@/executor/variables/resolver'
import { navigatePathAsync } from '@/executor/variables/resolvers/reference-async.server'
import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
const { mockStoreLargeValue } = vi.hoisted(() => ({ mockStoreLargeValue: vi.fn() }))
vi.mock('@/lib/execution/payloads/store', () => ({
storeLargeValue: mockStoreLargeValue,
materializeLargeValueRef: vi.fn(),
}))
function createBlock(id: string, name: string, type: string, params = {}): SerializedBlock {
return {
id,
metadata: { id: type, name },
position: { x: 0, y: 0 },
config: { tool: type, params },
inputs: {},
outputs: {
result: 'string',
items: 'json',
file: 'file',
},
enabled: true,
}
}
function createTestManifest(totalCount = 100_000): LargeArrayManifest {
return {
__simLargeArrayManifest: true,
version: LARGE_ARRAY_MANIFEST_VERSION,
kind: 'array',
totalCount,
chunkCount: 1,
byteSize: 12 * 1024 * 1024,
chunks: [
{
count: totalCount,
byteSize: 12 * 1024 * 1024,
ref: {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'array',
size: 12 * 1024 * 1024,
key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json',
executionId: 'execution-1',
},
},
],
preview: [{ key: 'SIM-0' }],
}
}
function createResolver(
language = 'javascript',
options: ConstructorParameters<typeof VariableResolver>[3] = {}
) {
const producer = createBlock('producer', 'Producer', BlockType.API)
const functionBlock = createBlock('function', 'Function', BlockType.FUNCTION, {
language,
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [producer, functionBlock],
connections: [],
loops: {},
parallels: {},
}
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: 'hello world',
items: ['a', 'b'],
file: {
id: 'file-1',
name: 'image.png',
url: 'https://example.com/image.png',
key: 'execution/workspace-1/workflow-1/execution-1/image.png',
context: 'execution',
size: 12 * 1024 * 1024,
type: 'image/png',
base64: 'large-inline-base64',
},
})
const ctx = {
blockStates: state.getBlockStates(),
blockLogs: [],
environmentVariables: {},
workflowVariables: {},
decisions: { router: new Map(), condition: new Map() },
loopExecutions: new Map(),
executedBlocks: new Set(),
activeExecutionPath: new Set(),
completedLoops: new Set(),
metadata: {},
} as ExecutionContext
return {
block: functionBlock,
ctx,
resolver: new VariableResolver(workflow, {}, state, options),
}
}
describe('VariableResolver function block inputs', () => {
it('returns empty inputs when params are missing', async () => {
const { block, ctx, resolver } = createResolver()
const result = await resolver.resolveInputsForFunctionBlock(ctx, 'function', undefined, block)
expect(result).toEqual({ resolvedInputs: {}, displayInputs: {}, contextVariables: {} })
})
it('resolves JavaScript block references through globalThis context variables', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(result.resolvedInputs.code).toBe('return globalThis["__blockRef_0"]')
expect(result.displayInputs.code).toBe('return "hello world"')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('allows Variables block assignments to receive whole large refs', async () => {
const producer = createBlock('producer', 'Producer', BlockType.API)
const variablesBlock = createBlock('variables', 'Variables', BlockType.VARIABLES, {
variables: [
{
variableId: 'var-1',
variableName: 'issues',
type: 'array',
value: '<Producer.result>',
},
],
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [producer, variablesBlock],
connections: [],
loops: {},
parallels: {},
}
const state = new ExecutionState()
const ref = {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'array',
size: 12 * 1024 * 1024,
executionId: 'execution-1',
}
state.setBlockOutput('producer', { result: ref })
const ctx = {
blockStates: state.getBlockStates(),
blockLogs: [],
environmentVariables: {},
workflowVariables: {},
decisions: { router: new Map(), condition: new Map() },
loopExecutions: new Map(),
executedBlocks: new Set(),
activeExecutionPath: new Set(),
completedLoops: new Set(),
metadata: {},
} as ExecutionContext
const resolver = new VariableResolver(workflow, {}, state)
const result = await resolver.resolveInputs(
ctx,
'variables',
variablesBlock.config.params,
variablesBlock
)
expect(JSON.parse(result.variables[0].value)).toEqual(ref)
})
it('allows Variables block assignments to receive whole large array manifests', async () => {
const producer = createBlock('producer', 'Producer', BlockType.API)
const variablesBlock = createBlock('variables', 'Variables', BlockType.VARIABLES, {
variables: [
{
variableId: 'var-1',
variableName: 'issues',
type: 'array',
value: '<Producer.result>',
},
],
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [producer, variablesBlock],
connections: [],
loops: {},
parallels: {},
}
const state = new ExecutionState()
const manifest = createTestManifest()
state.setBlockOutput('producer', { result: manifest })
const ctx = {
blockStates: state.getBlockStates(),
blockLogs: [],
environmentVariables: {},
workflowVariables: {},
decisions: { router: new Map(), condition: new Map() },
loopExecutions: new Map(),
executedBlocks: new Set(),
activeExecutionPath: new Set(),
completedLoops: new Set(),
metadata: {},
} as ExecutionContext
const resolver = new VariableResolver(workflow, {}, state)
const result = await resolver.resolveInputs(
ctx,
'variables',
variablesBlock.config.params,
variablesBlock
)
expect(JSON.parse(result.variables[0].value)).toEqual(manifest)
})
it('allows Response block data to preserve whole large refs', async () => {
const producer = createBlock('producer', 'Producer', BlockType.API)
const responseBlock = createBlock('response', 'Response', BlockType.RESPONSE, {
dataMode: 'json',
data: '<Producer.result>',
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [producer, responseBlock],
connections: [],
loops: {},
parallels: {},
}
const state = new ExecutionState()
const ref = {
__simLargeValueRef: true,
version: 1,
id: 'lv_ZYXWVUTSRQPO',
kind: 'array',
size: 12 * 1024 * 1024,
executionId: 'execution-1',
}
state.setBlockOutput('producer', { result: ref })
const ctx = {
blockStates: state.getBlockStates(),
blockLogs: [],
environmentVariables: {},
workflowVariables: {},
decisions: { router: new Map(), condition: new Map() },
loopExecutions: new Map(),
executedBlocks: new Set(),
activeExecutionPath: new Set(),
completedLoops: new Set(),
metadata: {},
} as ExecutionContext
const resolver = new VariableResolver(workflow, {}, state)
const result = await resolver.resolveInputs(
ctx,
'response',
responseBlock.config.params,
responseBlock
)
expect(JSON.parse(result.data)).toEqual(ref)
})
it('resolves workflow variable object references through context variables', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const issues = [{ key: 'SIM-1', summary: 'Small issue' }]
ctx.workflowVariables = {
'var-1': { id: 'var-1', name: 'issues', type: 'array', value: issues },
}
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <variable.issues>' },
block
)
expect(result.resolvedInputs.code).toBe('return globalThis["__blockRef_0"]')
expect(result.displayInputs.code).toBe('return [{"key":"SIM-1","summary":"Small issue"}]')
expect(result.contextVariables).toEqual({ __blockRef_0: issues })
})
it('resolves large workflow variable refs without embedding large literals', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const ref = {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'array',
size: 12 * 1024 * 1024,
executionId: 'execution-1',
}
ctx.workflowVariables = {
'var-1': { id: 'var-1', name: 'issues', type: 'array', value: ref },
}
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <variable.issues>' },
block
)
expect(result.resolvedInputs.code).toBe(
'return (await sim.values.read(globalThis["__blockRef_0"]))'
)
expect(result.contextVariables).toEqual({ __blockRef_0: ref })
})
it('rewrites whole manifest workflow variables to lazy JavaScript array reads', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const manifest = createTestManifest()
ctx.workflowVariables = {
'var-1': { id: 'var-1', name: 'issues', type: 'array', value: manifest },
}
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <variable.issues>' },
block
)
expect(result.resolvedInputs.code).toBe(
'return (await sim.values.readArray(globalThis["__blockRef_0"]))'
)
expect(result.contextVariables).toEqual({ __blockRef_0: manifest })
})
it('resolves manifest workflow variable length without whole-array context variables', async () => {
const { block, ctx, resolver } = createResolver('javascript', { navigatePathAsync })
const manifest = createTestManifest()
ctx.workflowVariables = {
'var-1': { id: 'var-1', name: 'issues', type: 'array', value: manifest },
}
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <variable.issues.length>' },
block
)
expect(result.resolvedInputs.code).toBe('return 100000')
expect(result.contextVariables).toEqual({})
})
it('keeps manifest internals hidden during async path navigation', async () => {
const { ctx } = createResolver()
const manifest = createTestManifest()
await expect(navigatePathAsync(manifest, ['totalCount'], ctx)).resolves.toBe(100_000)
await expect(navigatePathAsync(manifest, ['chunkCount'], ctx)).resolves.toBe(1)
await expect(navigatePathAsync(manifest, ['preview'], ctx)).resolves.toEqual([{ key: 'SIM-0' }])
await expect(
navigatePathAsync(manifest, ['chunks', '0', 'ref', 'id'], ctx)
).resolves.toBeUndefined()
})
it('resolves indexed manifest workflow variable paths without whole-array context variables', async () => {
const manifest = createTestManifest()
const navigateManifestPath = vi.fn(async () => 'SIM-0')
const { block, ctx, resolver } = createResolver('javascript', {
navigatePathAsync: navigateManifestPath,
})
ctx.workflowVariables = {
'var-1': { id: 'var-1', name: 'issues', type: 'array', value: manifest },
}
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <variable.issues[0].key>' },
block
)
expect(navigateManifestPath).toHaveBeenCalledWith(
manifest,
['0', 'key'],
expect.objectContaining({ allowLargeValueRefs: true })
)
expect(result.resolvedInputs.code).toBe('return "SIM-0"')
expect(result.contextVariables).toEqual({})
})
it('resolves named loop result bracket paths in function code', async () => {
const loopBlock = createBlock('loop-1', 'Loop 1', 'loop')
const functionBlock = createBlock('function', 'Function', BlockType.FUNCTION, {
language: 'javascript',
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [loopBlock, functionBlock],
connections: [],
loops: { 'loop-1': { nodes: ['producer'] } },
parallels: {},
}
const state = new ExecutionState()
state.setBlockOutput('loop-1', {
results: [[{ id: 'a' }], [{ id: 'b' }]],
})
const ctx = {
blockStates: state.getBlockStates(),
blockLogs: [],
environmentVariables: {},
workflowVariables: {},
decisions: { router: new Map(), condition: new Map() },
loopExecutions: new Map(),
executedBlocks: new Set(),
activeExecutionPath: new Set(),
completedLoops: new Set(),
metadata: {},
} as ExecutionContext
const resolver = new VariableResolver(workflow, {}, state)
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <loop1.results[1][0].id>' },
functionBlock
)
expect(result.resolvedInputs.code).toBe('return globalThis["__blockRef_0"]')
expect(result.displayInputs.code).toBe('return "b"')
expect(result.contextVariables).toEqual({ __blockRef_0: 'b' })
})
it('rewrites JavaScript file base64 references to lazy runtime reads', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'const base64 = <Producer.file.base64>;\nreturn base64' },
block
)
expect(result.resolvedInputs.code).toBe(
'const base64 = (await sim.files.readBase64(globalThis["__blockRef_0"]));\nreturn base64'
)
expect(result.displayInputs.code).toBe('const base64 = <Producer.file.base64>;\nreturn base64')
expect(result.contextVariables.__blockRef_0).toMatchObject({
id: 'file-1',
name: 'image.png',
})
expect(result.contextVariables.__blockRef_0).not.toHaveProperty('base64')
})
it('wraps lazy JavaScript file base64 reads before member access', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <Producer.file.base64>.length' },
block
)
expect(result.resolvedInputs.code).toBe(
'return (await sim.files.readBase64(globalThis["__blockRef_0"])).length'
)
})
it('uses existing inline base64 for keyless files instead of lazy storage reads', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const state = new ExecutionState()
state.setBlockOutput('producer', {
file: {
id: 'file-keyless',
name: 'inline.txt',
key: '',
url: 'https://example.com/inline.txt',
size: 5,
type: 'text/plain',
base64: 'aGVsbG8=',
},
})
const keylessResolver = new VariableResolver(
{
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
},
{},
state
)
const result = await keylessResolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <Producer.file.base64>' },
block
)
expect(result.resolvedInputs.code).toBe('return globalThis["__blockRef_0"]')
expect(result.contextVariables.__blockRef_0).toBe('aGVsbG8=')
})
it('rewrites loop current item base64 references to lazy runtime reads', async () => {
const functionBlock = createBlock('function', 'Function', BlockType.FUNCTION, {
language: 'javascript',
})
const loopBlock = createBlock('loop-1', 'Loop 1', 'loop')
const workflow: SerializedWorkflow = {
version: '1',
blocks: [loopBlock, functionBlock],
connections: [],
loops: { 'loop-1': { id: 'loop-1', nodes: ['function'], iterations: 1 } },
parallels: {},
}
const state = new ExecutionState()
const file = {
id: 'file-loop',
name: 'loop.png',
url: 'https://example.com/loop.png',
key: 'execution/workspace-1/workflow-1/execution-1/loop.png',
context: 'execution',
size: 12 * 1024 * 1024,
type: 'image/png',
base64: 'large-inline-base64',
}
const ctx = {
...createResolver().ctx,
loopExecutions: new Map([
[
'loop-1',
{
iteration: 0,
currentIterationOutputs: new Map(),
allIterationOutputs: [],
item: file,
items: [file],
},
],
]),
} as ExecutionContext
const resolver = new VariableResolver(workflow, {}, state)
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <loop.currentItem.base64>.length' },
functionBlock
)
expect(result.resolvedInputs.code).toBe(
'return (await sim.files.readBase64(globalThis["__blockRef_0"])).length'
)
expect(result.contextVariables.__blockRef_0).toMatchObject({ id: 'file-loop' })
expect(result.contextVariables.__blockRef_0).not.toHaveProperty('base64')
})
it('rewrites parallel current item base64 references to lazy runtime reads', async () => {
const functionBlock = createBlock('function', 'Function', BlockType.FUNCTION, {
language: 'javascript',
})
const parallelBlock = createBlock('parallel-1', 'Parallel 1', 'parallel')
const workflow: SerializedWorkflow = {
version: '1',
blocks: [parallelBlock, functionBlock],
connections: [],
loops: {},
parallels: {
'parallel-1': {
id: 'parallel-1',
nodes: ['function'],
parallelType: 'collection',
distribution: [],
},
},
}
const state = new ExecutionState()
const file = {
id: 'file-parallel',
name: 'parallel.png',
url: 'https://example.com/parallel.png',
key: 'execution/workspace-1/workflow-1/execution-1/parallel.png',
context: 'execution',
size: 12 * 1024 * 1024,
type: 'image/png',
base64: 'large-inline-base64',
}
const ctx = {
...createResolver().ctx,
parallelExecutions: new Map([
[
'parallel-1',
{
parallelId: 'parallel-1',
totalBranches: 1,
branchOutputs: new Map(),
items: [{ file }],
},
],
]),
parallelBlockMapping: new Map([
['function', { originalBlockId: 'function', parallelId: 'parallel-1', iterationIndex: 0 }],
]),
} as ExecutionContext
const resolver = new VariableResolver(workflow, {}, state)
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <parallel.currentItem.file.base64>.length' },
functionBlock
)
expect(result.resolvedInputs.code).toBe(
'return (await sim.files.readBase64(globalThis["__blockRef_0"])).length'
)
expect(result.contextVariables.__blockRef_0).toMatchObject({ id: 'file-parallel' })
expect(result.contextVariables.__blockRef_0).not.toHaveProperty('base64')
})
it('rewrites JavaScript large value refs to lazy runtime reads', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'object',
size: 12 * 1024 * 1024,
key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json',
executionId: 'execution-1',
},
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
}
const largeResolver = new VariableResolver(workflow, {}, state)
const largeCtx = {
...ctx,
blockStates: state.getBlockStates(),
} as ExecutionContext
const result = await largeResolver.resolveInputsForFunctionBlock(
largeCtx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(result.resolvedInputs.code).toBe(
'return (await sim.values.read(globalThis["__blockRef_0"]))'
)
expect(result.contextVariables.__blockRef_0).toMatchObject({
__simLargeValueRef: true,
id: 'lv_ABCDEFGHIJKL',
})
})
it('fails whole large value refs for Function runtimes without lazy helpers', async () => {
const { block, ctx } = createResolver('python')
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'object',
size: 12 * 1024 * 1024,
key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json',
executionId: 'execution-1',
},
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
}
const largeResolver = new VariableResolver(workflow, {}, state)
const largeCtx = {
...ctx,
blockStates: state.getBlockStates(),
} as ExecutionContext
await expect(
largeResolver.resolveInputsForFunctionBlock(
largeCtx,
'function',
{ code: 'return <Producer.result>' },
block
)
).rejects.toThrow('This execution value is too large to inline')
})
it('fails whole large array manifests for Function runtimes without lazy helpers', async () => {
const { block, ctx } = createResolver('python')
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: createTestManifest(),
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
}
const largeResolver = new VariableResolver(workflow, {}, state)
const largeCtx = {
...ctx,
blockStates: state.getBlockStates(),
} as ExecutionContext
await expect(
largeResolver.resolveInputsForFunctionBlock(
largeCtx,
'function',
{ code: 'return <Producer.result>' },
block
)
).rejects.toThrow('This execution value contains nested large values')
})
it('fails whole large value refs for JavaScript with imports', async () => {
const { block, ctx } = createResolver('javascript')
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'object',
size: 12 * 1024 * 1024,
key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json',
executionId: 'execution-1',
},
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
}
const largeResolver = new VariableResolver(workflow, {}, state)
const largeCtx = {
...ctx,
blockStates: state.getBlockStates(),
} as ExecutionContext
await expect(
largeResolver.resolveInputsForFunctionBlock(
largeCtx,
'function',
{ code: "import x from 'x'\nreturn <Producer.result>" },
block
)
).rejects.toThrow('This execution value is too large to inline')
})
it('keeps JavaScript lazy helpers enabled when import appears in comments or strings', async () => {
const { block, ctx } = createResolver('javascript')
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'object',
size: 12 * 1024 * 1024,
key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json',
executionId: 'execution-1',
},
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
}
const largeResolver = new VariableResolver(workflow, {}, state)
const largeCtx = {
...ctx,
blockStates: state.getBlockStates(),
} as ExecutionContext
const result = await largeResolver.resolveInputsForFunctionBlock(
largeCtx,
'function',
{
code: "/** @import { Foo } from 'foo' */\nconst text = \"import bar from 'bar'\"\nreturn <Producer.result>",
},
block
)
expect(result.resolvedInputs.code).toBe(
'/** @import { Foo } from \'foo\' */\nconst text = "import bar from \'bar\'"\nreturn (await sim.values.read(globalThis["__blockRef_0"]))'
)
})
it('keeps JavaScript lazy helpers enabled for dynamic import expressions', async () => {
const { block, ctx } = createResolver('javascript')
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'object',
size: 12 * 1024 * 1024,
key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json',
executionId: 'execution-1',
},
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
}
const largeResolver = new VariableResolver(workflow, {}, state)
const largeCtx = {
...ctx,
blockStates: state.getBlockStates(),
} as ExecutionContext
const result = await largeResolver.resolveInputsForFunctionBlock(
largeCtx,
'function',
{ code: "const mod = import('foo')\nreturn <Producer.result>" },
block
)
expect(result.resolvedInputs.code).toBe(
'const mod = import(\'foo\')\nreturn (await sim.values.read(globalThis["__blockRef_0"]))'
)
})
it('fails nested large value refs for Function runtimes without lazy helpers', async () => {
const { block, ctx } = createResolver('python')
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: {
rows: {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'array',
size: 12 * 1024 * 1024,
key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json',
executionId: 'execution-1',
},
},
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
}
const largeResolver = new VariableResolver(workflow, {}, state)
const largeCtx = {
...ctx,
blockStates: state.getBlockStates(),
} as ExecutionContext
await expect(
largeResolver.resolveInputsForFunctionBlock(
largeCtx,
'function',
{ code: 'return <Producer.result>' },
block
)
).rejects.toThrow('This execution value contains nested large values')
})
it('fails nested large value refs for JavaScript instead of leaking ref markers', async () => {
const { block, ctx } = createResolver('javascript')
const state = new ExecutionState()
state.setBlockOutput('producer', {
result: {
rows: {
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'array',
size: 12 * 1024 * 1024,
key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json',
executionId: 'execution-1',
},
},
})
const workflow: SerializedWorkflow = {
version: '1',
blocks: [createBlock('producer', 'Producer', BlockType.API), block],
connections: [],
loops: {},
parallels: {},
}
const largeResolver = new VariableResolver(workflow, {}, state)
const largeCtx = {
...ctx,
blockStates: state.getBlockStates(),
} as ExecutionContext
await expect(
largeResolver.resolveInputsForFunctionBlock(
largeCtx,
'function',
{ code: 'return <Producer.result>.rows.length' },
block
)
).rejects.toThrow('This execution value contains nested large values')
})
it('resolves Python block references through globals lookup', async () => {
const { block, ctx, resolver } = createResolver('python')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(result.resolvedInputs.code).toBe('return globals()["__blockRef_0"]')
expect(result.displayInputs.code).toBe('return "hello world"')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('breaks JavaScript string literals around quoted block references', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: "const rawEmail = '<Producer.result>';\nreturn rawEmail" },
block
)
expect(result.resolvedInputs.code).toBe(
"const rawEmail = '' + JSON.stringify(globalThis[\"__blockRef_0\"]) + '';\nreturn rawEmail"
)
expect(result.displayInputs.code).toBe('const rawEmail = \'"hello world"\';\nreturn rawEmail')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('uses template interpolation for JavaScript template literal block references', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return `value: <Producer.result>`' },
block
)
expect(result.resolvedInputs.code).toBe(
// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional — asserting template literal is preserved
'return `value: ${JSON.stringify(globalThis["__blockRef_0"])}`'
)
expect(result.displayInputs.code).toBe('return `value: "hello world"`')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('keeps JavaScript block references inside template expressions executable', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional — asserting template literal is preserved
{ code: 'return `${String(<Producer.result>)}`' },
block
)
// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional — asserting template literal is preserved
expect(result.resolvedInputs.code).toBe('return `${String(globalThis["__blockRef_0"])}`')
// biome-ignore lint/suspicious/noTemplateCurlyInString: intentional — asserting template literal is preserved
expect(result.displayInputs.code).toBe('return `${String("hello world")}`')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('ignores JavaScript comment quotes before later block references', async () => {
const { block, ctx, resolver } = createResolver('javascript')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: "// don't confuse quote tracking\nreturn <Producer.result>" },
block
)
expect(result.resolvedInputs.code).toBe(
'// don\'t confuse quote tracking\nreturn globalThis["__blockRef_0"]'
)
expect(result.displayInputs.code).toBe('// don\'t confuse quote tracking\nreturn "hello world"')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('breaks Python string literals around quoted block references', async () => {
const { block, ctx, resolver } = createResolver('python')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: "raw_email = '<Producer.result>'\nreturn raw_email" },
block
)
expect(result.resolvedInputs.code).toBe(
"raw_email = '' + json.dumps(globals()[\"__blockRef_0\"]) + ''\nreturn raw_email"
)
expect(result.displayInputs.code).toBe('raw_email = \'"hello world"\'\nreturn raw_email')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('breaks Python triple-double-quoted strings around block references', async () => {
const { block, ctx, resolver } = createResolver('python')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'prompt = """\nSummary: <Producer.result>\n"""\nreturn prompt' },
block
)
expect(result.resolvedInputs.code).toBe(
'prompt = """\nSummary: """ + json.dumps(globals()["__blockRef_0"]) + """\n"""\nreturn prompt'
)
expect(result.displayInputs.code).toBe(
'prompt = """\nSummary: "hello world"\n"""\nreturn prompt'
)
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('ignores escaped triple-double quotes before later Python block references', async () => {
const { block, ctx, resolver } = createResolver('python')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'prompt = """Escaped delimiter: \\"\\"\\"\nSummary: <Producer.result>\n"""' },
block
)
expect(result.resolvedInputs.code).toBe(
'prompt = """Escaped delimiter: \\"\\"\\"\nSummary: """ + json.dumps(globals()["__blockRef_0"]) + """\n"""'
)
expect(result.displayInputs.code).toBe(
'prompt = """Escaped delimiter: \\"\\"\\"\nSummary: "hello world"\n"""'
)
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('breaks Python triple-single-quoted strings around block references', async () => {
const { block, ctx, resolver } = createResolver('python')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: "prompt = '''\nSummary: <Producer.result>\n'''\nreturn prompt" },
block
)
expect(result.resolvedInputs.code).toBe(
"prompt = '''\nSummary: ''' + json.dumps(globals()[\"__blockRef_0\"]) + '''\n'''\nreturn prompt"
)
expect(result.displayInputs.code).toBe(
"prompt = '''\nSummary: \"hello world\"\n'''\nreturn prompt"
)
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('ignores Python comment quotes before later block references', async () => {
const { block, ctx, resolver } = createResolver('python')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: "# don't confuse quote tracking\nreturn <Producer.result>" },
block
)
expect(result.resolvedInputs.code).toBe(
'# don\'t confuse quote tracking\nreturn globals()["__blockRef_0"]'
)
expect(result.displayInputs.code).toBe('# don\'t confuse quote tracking\nreturn "hello world"')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('uses separate Python context variables for repeated mutable references', async () => {
const { block, ctx, resolver } = createResolver('python')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'a = <Producer.items>\nb = <Producer.items>\nreturn b' },
block
)
expect(result.resolvedInputs.code).toBe(
'a = globals()["__blockRef_0"]\nb = globals()["__blockRef_1"]\nreturn b'
)
expect(result.displayInputs.code).toBe(
'a = json.loads("[\\"a\\",\\"b\\"]")\nb = json.loads("[\\"a\\",\\"b\\"]")\nreturn b'
)
expect(result.contextVariables).toEqual({
__blockRef_0: ['a', 'b'],
__blockRef_1: ['a', 'b'],
})
})
it('uses shell-safe expansions for block references', async () => {
const { block, ctx, resolver } = createResolver('shell')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'echo <Producer.result>suffix && echo "<Producer.result>"' },
block
)
expect(result.resolvedInputs.code).toBe(
`echo "\${__blockRef_0}"suffix && echo "\${__blockRef_1}"`
)
expect(result.displayInputs.code).toBe('echo "hello world"suffix && echo "hello world"')
expect(result.contextVariables).toEqual({
__blockRef_0: 'hello world',
__blockRef_1: 'hello world',
})
})
it('ignores shell comment quotes when formatting later block references', async () => {
const { block, ctx, resolver } = createResolver('shell')
const result = await resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: "# don't confuse quote tracking\necho <Producer.result>" },
block
)
expect(result.resolvedInputs.code).toBe(
`# don't confuse quote tracking\necho "\${__blockRef_0}"`
)
expect(result.displayInputs.code).toBe('# don\'t confuse quote tracking\necho "hello world"')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
})
describe('VariableResolver function context overflow offload', () => {
const REF_KEY = 'execution/workspace-1/workflow-1/execution-1/large-value-lv_ABCDEFGHIJKL.json'
function createOffloadEnv(language: string, producerOutput: Record<string, unknown>) {
const { block, ctx } = createResolver(language)
const producer = createBlock('producer', 'Producer', BlockType.API)
const state = new ExecutionState()
state.setBlockOutput('producer', producerOutput)
const workflow: SerializedWorkflow = {
version: '1',
blocks: [producer, block],
connections: [],
loops: {},
parallels: {},
}
const resolver = new VariableResolver(workflow, {}, state)
const durableCtx = {
...ctx,
blockStates: state.getBlockStates(),
workspaceId: 'workspace-1',
workflowId: 'workflow-1',
executionId: 'execution-1',
largeValueKeys: [] as string[],
} as ExecutionContext
return { block, resolver, durableCtx }
}
beforeEach(() => {
mockStoreLargeValue.mockReset()
mockStoreLargeValue.mockResolvedValue({
__simLargeValueRef: true,
version: 1,
id: 'lv_ABCDEFGHIJKL',
kind: 'string',
size: 4 * 1024 * 1024,
key: REF_KEY,
executionId: 'execution-1',
})
})
it('offloads an oversized inline value to a lazily-read large-value ref', async () => {
const big = 'x'.repeat(4 * 1024 * 1024)
const { block, resolver, durableCtx } = createOffloadEnv('javascript', { result: big })
const result = await resolver.resolveInputsForFunctionBlock(
durableCtx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(mockStoreLargeValue).toHaveBeenCalledTimes(1)
expect(result.resolvedInputs.code).toBe(
'return (await sim.values.read(globalThis["__blockRef_0"]))'
)
expect(result.contextVariables.__blockRef_0).toMatchObject({
__simLargeValueRef: true,
id: 'lv_ABCDEFGHIJKL',
})
// The bulky value must not be inlined into either the request data or display source,
// and the Input view shows a readable placeholder instead of the raw ref object.
expect(result.displayInputs.code.length).toBeLessThan(1024)
expect(result.displayInputs.code).not.toContain('__simLargeValueRef')
expect(result.displayInputs.code).toContain('large string')
// The route must be authorized to materialize the ref it is about to receive.
expect(durableCtx.largeValueKeys).toContain(REF_KEY)
})
it('offloads only the values that overflow the budget when several are merged', async () => {
// Each value's inline footprint (data + display ~= 2x) is ~4 MB. The first fits the
// ~6 MB budget and stays inline; the second overflows and is offloaded.
const half = 'y'.repeat(2 * 1024 * 1024)
const { block, resolver, durableCtx } = createOffloadEnv('javascript', {
first: half,
second: half,
})
const result = await resolver.resolveInputsForFunctionBlock(
durableCtx,
'function',
{ code: 'return [<Producer.first>, <Producer.second>]' },
block
)
// First value fits the budget and stays inline; the second overflows and is offloaded.
expect(mockStoreLargeValue).toHaveBeenCalledTimes(1)
expect(result.resolvedInputs.code).toBe(
'return [globalThis["__blockRef_0"], (await sim.values.read(globalThis["__blockRef_1"]))]'
)
expect(result.contextVariables.__blockRef_0).toBe(half)
expect(result.contextVariables.__blockRef_1).toMatchObject({ __simLargeValueRef: true })
})
it('keeps small inline values inline without offloading', async () => {
const { block, resolver, durableCtx } = createOffloadEnv('javascript', {
result: 'hello world',
})
const result = await resolver.resolveInputsForFunctionBlock(
durableCtx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(mockStoreLargeValue).not.toHaveBeenCalled()
expect(result.resolvedInputs.code).toBe('return globalThis["__blockRef_0"]')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('does not offload when the execution context cannot persist durably', async () => {
const big = 'x'.repeat(4 * 1024 * 1024)
const { block, resolver, durableCtx } = createOffloadEnv('javascript', { result: big })
durableCtx.executionId = undefined
const result = await resolver.resolveInputsForFunctionBlock(
durableCtx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(mockStoreLargeValue).not.toHaveBeenCalled()
expect(result.resolvedInputs.code).toBe('return globalThis["__blockRef_0"]')
})
it('does not offload for non-JavaScript runtimes that lack the read broker', async () => {
const big = 'x'.repeat(4 * 1024 * 1024)
const { block, resolver, durableCtx } = createOffloadEnv('python', { result: big })
const result = await resolver.resolveInputsForFunctionBlock(
durableCtx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(mockStoreLargeValue).not.toHaveBeenCalled()
expect(result.resolvedInputs.code).toBe('return globals()["__blockRef_0"]')
})
})