/** * @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[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 ' }, 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: '', }, ], }) 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: '', }, ], }) 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: '', }) 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 ' }, 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 ' }, 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 ' }, 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 ' }, 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 ' }, 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 ' }, 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 = ;\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 = ;\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 .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 ' }, 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 .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 .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 ' }, 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 ' }, 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 ' }, 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 " }, 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 ", }, 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 " }, 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 ' }, 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 .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 ' }, 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 = '';\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: `' }, 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()}`' }, 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 " }, 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 = ''\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: \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: \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: \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 " }, 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 = \nb = \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 suffix && echo ""' }, 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 " }, 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) { 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 ' }, 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 [, ]' }, 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 ' }, 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 ' }, 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 ' }, block ) expect(mockStoreLargeValue).not.toHaveBeenCalled() expect(result.resolvedInputs.code).toBe('return globals()["__blockRef_0"]') }) })