improvement(resolver): use context variables for block outputs in function block code (#4223)

* v0.6.29: login improvements, posthog telemetry (#4026)

* feat(posthog): Add tracking on mothership abort (#4023)

Co-authored-by: Theodore Li <theo@sim.ai>

* fix(login): fix captcha headers for manual login  (#4025)

* fix(signup): fix turnstile key loading

* fix(login): fix captcha header passing

* Catch user already exists, remove login form captcha

* fix: use context variables for block outputs in function block code

When a function block references another block's output via <BlockA.result>,
the executor previously embedded the full value as a JavaScript literal
directly in the code string. For large outputs (>50 KB), this caused the code
string to exceed the terminal console display limit, making inputs appear
truncated or replaced with { __simTruncated: true } in the UI.

Instead, block output references in function block code are now stored as
named global variables (__blockRef_N) in the isolated VM context. The code
string only contains the compact variable name, keeping it small regardless
of the referenced value size.

Loop/parallel/env/workflow references are still inlined as literals since
the API route has no way to resolve them independently.

The _runtimeContextVars key is filtered from sanitizeInputsForLog so it
does not appear in execution logs or SSE events.

Pre-resolved context variables are merged with any variables produced by
the API route resolveCodeVariables, with executor values taking precedence.

Fixes #4195

* fix: address Cursor and Greptile bot review comments

- Pass preResolvedContextVariables through to shellEnvs for Shell language
  (Cursor: shell loses pre-resolved block refs, executes against undefined vars)
- Remove duplicate CodeExecutionOutput interface declaration
  (Cursor + Greptile: dead duplicate declaration in tools/function/types.ts)
- Deduplicate identical block references in resolveCodeWithContextVars so the
  same <BlockA.result> reused multiple times shares one __blockRef_N slot
  (Greptile P2: avoid duplicating large payloads across the wire)

* fix: shell block references and complex env value serialization

Two follow-ups to the function-block context-variable refactor:

- resolveCodeWithContextVars now emits `$__blockRef_N` for shell
  function blocks so the script dereferences the env var injected
  by the executor. Other languages still receive the bare identifier.
- The function-execute route now JSON-stringifies non-primitive
  values when building shell env vars, replacing the previous
  `String(v)` call that produced `[object Object]` for objects/arrays.

Co-Authored-By: Octopus <liyuan851277048@icloud.com>

* fix lint

* review pass

* ignore shell comments

* update contract

* fix tests

---------

Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Theodore Li <theodoreqili@gmail.com>
Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: octo-patch <octo-patch@github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
This commit is contained in:
Octopus
2026-05-06 00:25:41 -07:00
committed by GitHub
co-authored by Waleed Theodore Li Siddharth Ganesan Vikhyath Mondreti octo-patch Vikhyath Mondreti
parent ae87481d83
commit 7b6aa728ae
12 changed files with 494 additions and 11 deletions
+34 -4
View File
@@ -587,6 +587,26 @@ function cleanStdout(stdout: string): string {
return stdout
}
/**
* Serializes a value for use as a shell environment variable. Strings pass through
* unchanged; primitives are coerced via `String`; objects, arrays, and other complex
* values are JSON-stringified so that referencing them via `$VAR` yields a useful
* representation instead of `[object Object]`. `null`/`undefined` become an empty
* string to match POSIX env semantics.
*/
function serializeForShellEnv(value: unknown, nullValue = ''): string {
if (value === null || value === undefined) return nullValue
if (typeof value === 'string') return value
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
return String(value)
}
try {
return JSON.stringify(value) ?? ''
} catch {
return String(value)
}
}
async function maybeExportSandboxFileToWorkspace(args: {
authUserId: string
workflowId?: string
@@ -722,6 +742,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
blockNameMapping = {},
blockOutputSchemas = {},
workflowVariables = {},
contextVariables: preResolvedContextVariables = {},
workflowId,
workspaceId,
isCustomTool = false,
@@ -746,6 +767,10 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
// For shell, env vars are injected as OS env vars via shellEnvs.
// Replace {{VAR}} placeholders with $VAR so the shell can access them natively.
resolvedCode = code.replace(/\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}/g, '$$$1')
// Carry pre-resolved block output variables (e.g. __blockRef_N) so they can be
// injected as shell env vars below. The executor replaces block references in the
// code with these names, so the values must be present at runtime.
contextVariables = { ...preResolvedContextVariables }
} else {
const codeResolution = resolveCodeVariables(
code,
@@ -758,7 +783,10 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
lang
)
resolvedCode = codeResolution.resolvedCode
contextVariables = codeResolution.contextVariables
// Merge pre-resolved block output variables from the executor. These take precedence
// because they were produced by the resolver using full execution-state context
// (including loop/parallel scope) and should not be overwritten.
contextVariables = { ...codeResolution.contextVariables, ...preResolvedContextVariables }
}
let jsImports = ''
@@ -783,10 +811,10 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
const shellEnvs: Record<string, string> = {}
for (const [k, v] of Object.entries(envVars)) {
shellEnvs[k] = String(v)
shellEnvs[k] = serializeForShellEnv(v)
}
for (const [k, v] of Object.entries(contextVariables)) {
shellEnvs[k] = String(v)
shellEnvs[k] = serializeForShellEnv(v, 'null')
}
logger.info(`[${requestId}] E2B shell execution`, {
@@ -893,7 +921,9 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
prologue += `const environmentVariables = JSON.parse(${JSON.stringify(JSON.stringify(envVars))});\n`
prologueLineCount++
for (const [k, v] of Object.entries(contextVariables)) {
prologue += `const ${k} = ${formatLiteralForCode(v, 'javascript')};\n`
prologue += `globalThis[${JSON.stringify(k)}] = ${formatLiteralForCode(v, 'javascript')};\n`
prologue += `const ${k} = globalThis[${JSON.stringify(k)}];\n`
prologueLineCount++
prologueLineCount++
}
+16 -3
View File
@@ -44,7 +44,10 @@ import {
} from '@/executor/utils/iteration-context'
import { isJSONString } from '@/executor/utils/json'
import { filterOutputForLog } from '@/executor/utils/output-filter'
import type { VariableResolver } from '@/executor/variables/resolver'
import {
FUNCTION_BLOCK_CONTEXT_VARS_KEY,
type VariableResolver,
} from '@/executor/variables/resolver'
import type { SerializedBlock } from '@/serializer/types'
import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants'
@@ -115,7 +118,13 @@ export class BlockExecutor {
await validateBlockType(ctx.userId, ctx.workspaceId, blockType, ctx)
}
resolvedInputs = this.resolver.resolveInputs(ctx, node.id, block.config.params, block)
if (block.metadata?.id === BlockType.FUNCTION) {
const { resolvedInputs: fnInputs, contextVariables } =
this.resolver.resolveInputsForFunctionBlock(ctx, node.id, block.config.params, block)
resolvedInputs = { ...fnInputs, [FUNCTION_BLOCK_CONTEXT_VARS_KEY]: contextVariables }
} else {
resolvedInputs = this.resolver.resolveInputs(ctx, node.id, block.config.params, block)
}
if (blockLog) {
blockLog.input = this.sanitizeInputsForLog(resolvedInputs)
@@ -428,7 +437,11 @@ export class BlockExecutor {
const result: Record<string, any> = {}
for (const [key, value] of Object.entries(inputs)) {
if (SYSTEM_SUBBLOCK_IDS.includes(key) || key === 'triggerMode') {
if (
SYSTEM_SUBBLOCK_IDS.includes(key) ||
key === 'triggerMode' ||
key === FUNCTION_BLOCK_CONTEXT_VARS_KEY
) {
continue
}
@@ -3,6 +3,7 @@ import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/execution/constants'
import { BlockType } from '@/executor/constants'
import { FunctionBlockHandler } from '@/executor/handlers/function/function-handler'
import type { ExecutionContext } from '@/executor/types'
import { FUNCTION_BLOCK_CONTEXT_VARS_KEY } from '@/executor/variables/resolver'
import type { SerializedBlock } from '@/serializer/types'
import { executeTool } from '@/tools'
@@ -73,10 +74,13 @@ describe('FunctionBlockHandler', () => {
blockData: {},
blockNameMapping: {},
blockOutputSchemas: {},
contextVariables: {},
_context: {
workflowId: mockContext.workflowId,
workspaceId: mockContext.workspaceId,
userId: mockContext.userId,
isDeployedContext: mockContext.isDeployedContext,
enforceCredentialAccess: mockContext.enforceCredentialAccess,
},
}
const expectedOutput: any = { result: 'Success' }
@@ -110,10 +114,13 @@ describe('FunctionBlockHandler', () => {
blockData: {},
blockNameMapping: {},
blockOutputSchemas: {},
contextVariables: {},
_context: {
workflowId: mockContext.workflowId,
workspaceId: mockContext.workspaceId,
userId: mockContext.userId,
isDeployedContext: mockContext.isDeployedContext,
enforceCredentialAccess: mockContext.enforceCredentialAccess,
},
}
const expectedOutput: any = { result: 'Success' }
@@ -140,10 +147,13 @@ describe('FunctionBlockHandler', () => {
blockData: {},
blockNameMapping: {},
blockOutputSchemas: {},
contextVariables: {},
_context: {
workflowId: mockContext.workflowId,
workspaceId: mockContext.workspaceId,
userId: mockContext.userId,
isDeployedContext: mockContext.isDeployedContext,
enforceCredentialAccess: mockContext.enforceCredentialAccess,
},
}
@@ -168,6 +178,24 @@ describe('FunctionBlockHandler', () => {
expect(mockExecuteTool).toHaveBeenCalled()
})
it('should pass runtime context variables to function_execute', async () => {
const contextVariables = { __blockRef_0: { result: 'from-block' } }
await handler.execute(mockContext, mockBlock, {
code: 'return globalThis["__blockRef_0"]',
[FUNCTION_BLOCK_CONTEXT_VARS_KEY]: contextVariables,
})
expect(mockExecuteTool).toHaveBeenCalledWith(
'function_execute',
expect.objectContaining({
contextVariables,
}),
false,
mockContext
)
})
it('should handle tool error with no specific message', async () => {
const inputs = { code: 'some code' }
const errorResult = { success: false }
@@ -3,6 +3,7 @@ import { DEFAULT_CODE_LANGUAGE } from '@/lib/execution/languages'
import { BlockType } from '@/executor/constants'
import type { BlockHandler, ExecutionContext } from '@/executor/types'
import { collectBlockData } from '@/executor/utils/block-data'
import { FUNCTION_BLOCK_CONTEXT_VARS_KEY } from '@/executor/variables/resolver'
import type { SerializedBlock } from '@/serializer/types'
import { executeTool } from '@/tools'
@@ -25,6 +26,9 @@ export class FunctionBlockHandler implements BlockHandler {
const { blockData, blockNameMapping, blockOutputSchemas } = collectBlockData(ctx)
const contextVariables =
(inputs[FUNCTION_BLOCK_CONTEXT_VARS_KEY] as Record<string, unknown> | undefined) ?? {}
const result = await executeTool(
'function_execute',
{
@@ -36,6 +40,7 @@ export class FunctionBlockHandler implements BlockHandler {
blockData,
blockNameMapping,
blockOutputSchemas,
contextVariables,
_context: {
workflowId: ctx.workflowId,
workspaceId: ctx.workspaceId,
@@ -143,14 +143,14 @@ export function createCombinedPattern(): RegExp {
*/
export function replaceValidReferences(
template: string,
replacer: (match: string) => string
replacer: (match: string, index: number, template: string) => string
): string {
const pattern = createReferencePattern()
return template.replace(pattern, (match) => {
return template.replace(pattern, (match, _content, index) => {
if (!isLikelyReferenceSegment(match)) {
return match
}
return replacer(match)
return replacer(match, index, template)
})
}
@@ -0,0 +1,153 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { BlockType } from '@/executor/constants'
import { ExecutionState } from '@/executor/execution/state'
import type { ExecutionContext } from '@/executor/types'
import { VariableResolver } from '@/executor/variables/resolver'
import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
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',
},
enabled: true,
}
}
function createResolver(language = 'javascript') {
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'],
})
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),
}
}
describe('VariableResolver function block inputs', () => {
it('returns empty inputs when params are missing', () => {
const { block, ctx, resolver } = createResolver()
const result = resolver.resolveInputsForFunctionBlock(ctx, 'function', undefined, block)
expect(result).toEqual({ resolvedInputs: {}, contextVariables: {} })
})
it('resolves JavaScript block references through globalThis context variables', () => {
const { block, ctx, resolver } = createResolver('javascript')
const result = resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(result.resolvedInputs.code).toBe('return globalThis["__blockRef_0"]')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('resolves Python block references through globals lookup', () => {
const { block, ctx, resolver } = createResolver('python')
const result = resolver.resolveInputsForFunctionBlock(
ctx,
'function',
{ code: 'return <Producer.result>' },
block
)
expect(result.resolvedInputs.code).toBe('return globals()["__blockRef_0"]')
expect(result.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
it('uses separate Python context variables for repeated mutable references', () => {
const { block, ctx, resolver } = createResolver('python')
const result = 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.contextVariables).toEqual({
__blockRef_0: ['a', 'b'],
__blockRef_1: ['a', 'b'],
})
})
it('uses shell-safe expansions for block references', () => {
const { block, ctx, resolver } = createResolver('shell')
const result = 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.contextVariables).toEqual({
__blockRef_0: 'hello world',
__blockRef_1: 'hello world',
})
})
it('ignores shell comment quotes when formatting later block references', () => {
const { block, ctx, resolver } = createResolver('shell')
const result = 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.contextVariables).toEqual({ __blockRef_0: 'hello world' })
})
})
+230
View File
@@ -16,8 +16,13 @@ import {
import { WorkflowResolver } from '@/executor/variables/resolvers/workflow'
import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
/** Key used to carry pre-resolved context variables through the inputs map. */
export const FUNCTION_BLOCK_CONTEXT_VARS_KEY = '_runtimeContextVars'
const logger = createLogger('VariableResolver')
type ShellQuoteContext = 'single' | 'double' | null
export class VariableResolver {
private resolvers: Resolver[]
private blockResolver: BlockResolver
@@ -37,6 +42,67 @@ export class VariableResolver {
]
}
/**
* Resolves inputs for function blocks. Block output references in the `code` field
* are stored as named context variables instead of being embedded as JavaScript
* literals, preventing large values from bloating the code string.
*
* Returns the resolved inputs and a `contextVariables` map. Callers should inject
* contextVariables into the function execution request body so the isolated VM can
* access them as global variables.
*/
resolveInputsForFunctionBlock(
ctx: ExecutionContext,
currentNodeId: string,
params: Record<string, any> | null | undefined,
block: SerializedBlock
): { resolvedInputs: Record<string, any>; contextVariables: Record<string, unknown> } {
const contextVariables: Record<string, unknown> = {}
const resolved: Record<string, any> = {}
if (!params) {
return { resolvedInputs: resolved, contextVariables }
}
for (const [key, value] of Object.entries(params)) {
if (key === 'code') {
if (typeof value === 'string') {
resolved[key] = this.resolveCodeWithContextVars(
ctx,
currentNodeId,
value,
undefined,
block,
contextVariables
)
} else if (Array.isArray(value)) {
resolved[key] = value.map((item: any) => {
if (item && typeof item === 'object' && typeof item.content === 'string') {
return {
...item,
content: this.resolveCodeWithContextVars(
ctx,
currentNodeId,
item.content,
undefined,
block,
contextVariables
),
}
}
return item
})
} else {
resolved[key] = this.resolveValue(ctx, currentNodeId, value, undefined, block)
}
} else {
resolved[key] = this.resolveValue(ctx, currentNodeId, value, undefined, block)
}
}
return { resolvedInputs: resolved, contextVariables }
}
resolveInputs(
ctx: ExecutionContext,
currentNodeId: string,
@@ -150,6 +216,170 @@ export class VariableResolver {
}
return value
}
/**
* Resolves a code template for a function block. Block output references are stored
* in `contextVarAccumulator` as named variables (e.g. `__blockRef_0`) and replaced
* with those variable names in the returned code string. Non-block references (loop
* items, workflow variables, env vars) are still inlined as literals so they remain
* available without any extra passing mechanism.
*/
private resolveCodeWithContextVars(
ctx: ExecutionContext,
currentNodeId: string,
template: string,
loopScope: LoopScope | undefined,
block: SerializedBlock,
contextVarAccumulator: Record<string, unknown>
): string {
const resolutionContext: ResolutionContext = {
executionContext: ctx,
executionState: this.state,
currentNodeId,
loopScope,
}
const language = (block.config?.params as Record<string, unknown> | undefined)?.language as
| string
| undefined
let replacementError: Error | null = null
let result = replaceValidReferences(template, (match, index) => {
if (replacementError) return match
try {
if (this.blockResolver.canResolve(match)) {
const resolved = this.resolveReference(match, resolutionContext)
if (resolved === undefined) return match
const effectiveValue = resolved === RESOLVED_EMPTY ? null : resolved
// Block output: store in contextVarAccumulator and replace the reference
// with language-specific runtime access to that stored value.
const varName = `__blockRef_${Object.keys(contextVarAccumulator).length}`
contextVarAccumulator[varName] = effectiveValue
const replacement = this.formatContextVariableReference(
varName,
language,
template,
index,
effectiveValue
)
return replacement
}
const resolved = this.resolveReference(match, resolutionContext)
if (resolved === undefined) return match
const effectiveValue = resolved === RESOLVED_EMPTY ? null : resolved
// Non-block reference (loop, parallel, workflow, env): embed as literal
return this.blockResolver.formatValueForBlock(effectiveValue, BlockType.FUNCTION, language)
} catch (error) {
replacementError = error instanceof Error ? error : new Error(String(error))
return match
}
})
if (replacementError !== null) {
throw replacementError
}
result = result.replace(createEnvVarPattern(), (match) => {
const resolved = this.resolveReference(match, resolutionContext)
return typeof resolved === 'string' ? resolved : match
})
return result
}
private formatContextVariableReference(
varName: string,
language: string | undefined,
template: string,
matchIndex: number,
value: unknown
): string {
if (language === 'python') {
return `globals()[${JSON.stringify(varName)}]`
}
if (language === 'shell') {
return this.formatShellContextVariableReference(varName, template, matchIndex, value)
}
return `globalThis[${JSON.stringify(varName)}]`
}
private formatShellContextVariableReference(
varName: string,
template: string,
matchIndex: number,
value: unknown
): string {
const expansion = `\${${varName}}`
const quoteContext = this.getShellQuoteContext(template, matchIndex)
if (quoteContext === 'double') {
return expansion
}
const shouldQuote =
quoteContext === 'single' ||
typeof value === 'string' ||
(typeof value === 'object' && value !== null) ||
Array.isArray(value)
if (!shouldQuote) {
return expansion
}
const quotedExpansion = `"${expansion}"`
if (quoteContext === 'single') {
return `'${quotedExpansion}'`
}
return quotedExpansion
}
private getShellQuoteContext(template: string, index: number): ShellQuoteContext {
let quoteContext: ShellQuoteContext = null
for (let i = 0; i < index; i++) {
const char = template[i]
if (quoteContext === null && this.isShellCommentStart(template, i)) {
const nextNewline = template.indexOf('\n', i + 1)
if (nextNewline === -1 || nextNewline >= index) {
break
}
i = nextNewline
continue
}
if (char === '\\' && quoteContext !== 'single') {
i++
continue
}
if (char === "'" && quoteContext !== 'double') {
quoteContext = quoteContext === 'single' ? null : 'single'
} else if (char === '"' && quoteContext !== 'single') {
quoteContext = quoteContext === 'double' ? null : 'double'
}
}
return quoteContext
}
private isShellCommentStart(template: string, index: number): boolean {
if (template[index] !== '#') {
return false
}
const previous = template[index - 1]
return previous === undefined || /\s|[;&|()<>]/.test(previous)
}
private resolveTemplate(
ctx: ExecutionContext,
currentNodeId: string,
+1
View File
@@ -99,6 +99,7 @@ export const functionExecuteContract = defineRouteContract({
blockNameMapping: z.record(z.string(), z.string()).optional().default({}),
blockOutputSchemas: z.record(z.string(), unknownRecordSchema).optional().default({}),
workflowVariables: unknownRecordSchema.optional().default({}),
contextVariables: unknownRecordSchema.optional().default({}),
workflowId: z.string().optional(),
workspaceId: z.string().optional(),
userId: z.string().optional(),
+21
View File
@@ -56,10 +56,17 @@ describe('Function Execute Tool', () => {
blockData: {},
blockNameMapping: {},
blockOutputSchemas: {},
contextVariables: {},
isCustomTool: false,
language: 'javascript',
outputFormat: undefined,
outputMimeType: undefined,
outputPath: undefined,
outputSandboxPath: undefined,
outputTable: undefined,
timeout: 5000,
workflowId: undefined,
workspaceId: undefined,
userId: undefined,
})
})
@@ -85,9 +92,16 @@ describe('Function Execute Tool', () => {
blockData: {},
blockNameMapping: {},
blockOutputSchemas: {},
contextVariables: {},
isCustomTool: false,
language: 'javascript',
outputFormat: undefined,
outputMimeType: undefined,
outputPath: undefined,
outputSandboxPath: undefined,
outputTable: undefined,
workflowId: undefined,
workspaceId: undefined,
userId: undefined,
})
})
@@ -105,9 +119,16 @@ describe('Function Execute Tool', () => {
blockData: {},
blockNameMapping: {},
blockOutputSchemas: {},
contextVariables: {},
isCustomTool: false,
language: 'javascript',
outputFormat: undefined,
outputMimeType: undefined,
outputPath: undefined,
outputSandboxPath: undefined,
outputTable: undefined,
workflowId: undefined,
workspaceId: undefined,
userId: undefined,
})
})
+1
View File
@@ -128,6 +128,7 @@ export const functionExecuteTool: ToolConfig<CodeExecutionInput, CodeExecutionOu
blockData: params.blockData || {},
blockNameMapping: params.blockNameMapping || {},
blockOutputSchemas: params.blockOutputSchemas || {},
contextVariables: params.contextVariables || {},
workflowId: params._context?.workflowId,
userId: params._context?.userId,
workspaceId: params._context?.workspaceId,
+2
View File
@@ -17,6 +17,8 @@ export interface CodeExecutionInput {
blockData?: Record<string, unknown>
blockNameMapping?: Record<string, string>
blockOutputSchemas?: Record<string, Record<string, unknown>>
/** Pre-resolved block output variables from the executor, injected as VM globals. */
contextVariables?: Record<string, unknown>
_context?: {
workflowId?: string
userId?: string
-1
View File
@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "simstudio",