mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
0.3.49: readme updates, router block and variables improvements
This commit is contained in:
@@ -159,7 +159,7 @@ bun run dev:sockets
|
||||
Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:
|
||||
|
||||
- Go to https://sim.ai → Settings → Copilot and generate a Copilot API key
|
||||
- Set `COPILOT_API_KEY` in your self-hosted environment to that value
|
||||
- Set `COPILOT_API_KEY` environment variable in your self-hosted apps/sim/.env file to that value
|
||||
|
||||
## Tech Stack
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ Your API key for the selected LLM provider. This is securely stored and used for
|
||||
|
||||
After a router makes a decision, you can access its outputs:
|
||||
|
||||
- **`<router.content>`**: Summary of the routing decision made
|
||||
- **`<router.prompt>`**: Summary of the routing prompt used
|
||||
- **`<router.selected_path>`**: Details of the chosen destination block
|
||||
- **`<router.tokens>`**: Token usage statistics from the LLM
|
||||
- **`<router.model>`**: The model used for decision-making
|
||||
@@ -182,7 +182,7 @@ Confidence Threshold: 0.7 // Minimum confidence for routing
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>router.content</strong>: Summary of routing decision
|
||||
<strong>router.prompt</strong>: Summary of routing prompt used
|
||||
</li>
|
||||
<li>
|
||||
<strong>router.selected_path</strong>: Details of chosen destination
|
||||
|
||||
@@ -18,7 +18,7 @@ const getCurrentOllamaModels = () => {
|
||||
|
||||
interface RouterResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
prompt: string
|
||||
model: string
|
||||
tokens?: {
|
||||
prompt?: number
|
||||
@@ -198,7 +198,6 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
|
||||
hidden: true,
|
||||
min: 0,
|
||||
max: 2,
|
||||
value: () => '0.1',
|
||||
},
|
||||
{
|
||||
id: 'systemPrompt',
|
||||
@@ -246,7 +245,7 @@ export const RouterBlock: BlockConfig<RouterResponse> = {
|
||||
},
|
||||
},
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Routing response content' },
|
||||
prompt: { type: 'string', description: 'Routing prompt used' },
|
||||
model: { type: 'string', description: 'Model used' },
|
||||
tokens: { type: 'json', description: 'Token usage' },
|
||||
cost: { type: 'json', description: 'Cost information' },
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE "workflow_schedule" DROP COLUMN "timezone";
|
||||
@@ -119,7 +119,7 @@ describe('RouterBlockHandler', () => {
|
||||
const inputs = {
|
||||
prompt: 'Choose the best option.',
|
||||
model: 'gpt-4o',
|
||||
temperature: 0.5,
|
||||
temperature: 0.1,
|
||||
}
|
||||
|
||||
const expectedTargetBlocks = [
|
||||
@@ -168,11 +168,11 @@ describe('RouterBlockHandler', () => {
|
||||
model: 'gpt-4o',
|
||||
systemPrompt: 'Generated System Prompt',
|
||||
context: JSON.stringify([{ role: 'user', content: 'Choose the best option.' }]),
|
||||
temperature: 0.5,
|
||||
temperature: 0.1,
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
content: 'Choose the best option.',
|
||||
prompt: 'Choose the best option.',
|
||||
model: 'mock-model',
|
||||
tokens: { prompt: 100, completion: 5, total: 105 },
|
||||
cost: {
|
||||
@@ -233,7 +233,7 @@ describe('RouterBlockHandler', () => {
|
||||
const requestBody = JSON.parse(fetchCallArgs[1].body)
|
||||
expect(requestBody).toMatchObject({
|
||||
model: 'gpt-4o',
|
||||
temperature: 0,
|
||||
temperature: 0.1,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export class RouterBlockHandler implements BlockHandler {
|
||||
model: routerConfig.model,
|
||||
systemPrompt: systemPrompt,
|
||||
context: JSON.stringify(messages),
|
||||
temperature: routerConfig.temperature,
|
||||
temperature: 0.1,
|
||||
apiKey: routerConfig.apiKey,
|
||||
workflowId: context.workflowId,
|
||||
}
|
||||
@@ -102,7 +102,7 @@ export class RouterBlockHandler implements BlockHandler {
|
||||
)
|
||||
|
||||
return {
|
||||
content: inputs.prompt,
|
||||
prompt: inputs.prompt,
|
||||
model: result.model,
|
||||
tokens: {
|
||||
prompt: tokens.prompt || 0,
|
||||
|
||||
@@ -13,7 +13,6 @@ describe('InputResolver', () => {
|
||||
let resolver: InputResolver
|
||||
|
||||
beforeEach(() => {
|
||||
// Set up a sample workflow with different types of blocks
|
||||
sampleWorkflow = {
|
||||
version: '1.0',
|
||||
blocks: [
|
||||
@@ -64,7 +63,6 @@ describe('InputResolver', () => {
|
||||
},
|
||||
],
|
||||
connections: [
|
||||
// Add connections so blocks can reference each other
|
||||
{ source: 'starter-block', target: 'function-block' },
|
||||
{ source: 'function-block', target: 'condition-block' },
|
||||
{ source: 'condition-block', target: 'api-block' },
|
||||
@@ -73,10 +71,9 @@ describe('InputResolver', () => {
|
||||
loops: {},
|
||||
}
|
||||
|
||||
// Mock execution context
|
||||
mockContext = {
|
||||
workflowId: 'test-workflow',
|
||||
workflow: sampleWorkflow, // Add workflow reference
|
||||
workflow: sampleWorkflow,
|
||||
blockStates: new Map([
|
||||
['starter-block', { output: { input: 'Hello World', type: 'text' } }],
|
||||
['function-block', { output: { result: '42' } }], // String value as it would be in real app
|
||||
@@ -92,13 +89,11 @@ describe('InputResolver', () => {
|
||||
executedBlocks: new Set(['starter-block', 'function-block']),
|
||||
}
|
||||
|
||||
// Mock environment variables
|
||||
mockEnvironmentVars = {
|
||||
API_KEY: 'test-api-key',
|
||||
BASE_URL: 'https://api.example.com',
|
||||
}
|
||||
|
||||
// Mock workflow variables
|
||||
mockWorkflowVars = {
|
||||
stringVar: {
|
||||
id: 'var1',
|
||||
@@ -112,28 +107,28 @@ describe('InputResolver', () => {
|
||||
workflowId: 'test-workflow',
|
||||
name: 'numberVar',
|
||||
type: 'number',
|
||||
value: '42', // Stored as string but should be converted to number
|
||||
value: '42',
|
||||
},
|
||||
boolVar: {
|
||||
id: 'var3',
|
||||
workflowId: 'test-workflow',
|
||||
name: 'boolVar',
|
||||
type: 'boolean',
|
||||
value: 'true', // Stored as string but should be converted to boolean
|
||||
value: 'true',
|
||||
},
|
||||
objectVar: {
|
||||
id: 'var4',
|
||||
workflowId: 'test-workflow',
|
||||
name: 'objectVar',
|
||||
type: 'object',
|
||||
value: '{"name":"John","age":30}', // Stored as string but should be parsed to object
|
||||
value: '{"name":"John","age":30}',
|
||||
},
|
||||
arrayVar: {
|
||||
id: 'var5',
|
||||
workflowId: 'test-workflow',
|
||||
name: 'arrayVar',
|
||||
type: 'array',
|
||||
value: '[1,2,3]', // Stored as string but should be parsed to array
|
||||
value: '[1,2,3]',
|
||||
},
|
||||
plainVar: {
|
||||
id: 'var6',
|
||||
@@ -144,27 +139,21 @@ describe('InputResolver', () => {
|
||||
},
|
||||
}
|
||||
|
||||
// Create accessibility map for block references
|
||||
const accessibleBlocksMap = new Map<string, Set<string>>()
|
||||
// Allow all blocks to reference each other for testing
|
||||
const allBlockIds = sampleWorkflow.blocks.map((b) => b.id)
|
||||
// Add common test block IDs
|
||||
const testBlockIds = ['test-block', 'test-block-2', 'generic-block']
|
||||
const allIds = [...allBlockIds, ...testBlockIds]
|
||||
|
||||
// Set up accessibility for workflow blocks
|
||||
sampleWorkflow.blocks.forEach((block) => {
|
||||
const accessibleBlocks = new Set(allIds)
|
||||
accessibleBlocksMap.set(block.id, accessibleBlocks)
|
||||
})
|
||||
|
||||
// Set up accessibility for test blocks
|
||||
testBlockIds.forEach((testId) => {
|
||||
const accessibleBlocks = new Set(allIds)
|
||||
accessibleBlocksMap.set(testId, accessibleBlocks)
|
||||
})
|
||||
|
||||
// Create resolver
|
||||
resolver = new InputResolver(
|
||||
sampleWorkflow,
|
||||
mockEnvironmentVars,
|
||||
@@ -227,7 +216,7 @@ describe('InputResolver', () => {
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.directRef).toBe(42) // Should be converted to actual number
|
||||
expect(result.directRef).toBe(42)
|
||||
expect(result.interpolated).toBe('The number is 42')
|
||||
})
|
||||
|
||||
@@ -253,7 +242,7 @@ describe('InputResolver', () => {
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.directRef).toBe(true) // Should be converted to boolean
|
||||
expect(result.directRef).toBe(true)
|
||||
expect(result.interpolated).toBe('Is it true? true')
|
||||
})
|
||||
|
||||
@@ -277,7 +266,7 @@ describe('InputResolver', () => {
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.directRef).toEqual({ name: 'John', age: 30 }) // Should be parsed to object
|
||||
expect(result.directRef).toEqual({ name: 'John', age: 30 })
|
||||
})
|
||||
|
||||
it('should resolve plain text variables without quoting', () => {
|
||||
@@ -318,7 +307,7 @@ describe('InputResolver', () => {
|
||||
params: {
|
||||
starterRef: '<starter-block.input>',
|
||||
functionRef: '<function-block.result>',
|
||||
nameRef: '<Start.input>', // Reference by name
|
||||
nameRef: '<Start.input>',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
@@ -333,7 +322,7 @@ describe('InputResolver', () => {
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.starterRef).toBe('Hello World')
|
||||
expect(result.functionRef).toBe('42') // String representation
|
||||
expect(result.functionRef).toBe('42')
|
||||
expect(result.nameRef).toBe('Hello World') // Should resolve using block name
|
||||
})
|
||||
|
||||
@@ -371,7 +360,7 @@ describe('InputResolver', () => {
|
||||
config: {
|
||||
tool: 'generic',
|
||||
params: {
|
||||
inactiveRef: '<condition-block.result>', // Not in activeExecutionPath
|
||||
inactiveRef: '<condition-block.result>',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
@@ -381,17 +370,13 @@ describe('InputResolver', () => {
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
// Since the condition-block is not in the active execution path,
|
||||
// we expect it to be treated as inactive and return an empty string
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
expect(result.inactiveRef).toBe('')
|
||||
})
|
||||
|
||||
it('should throw an error for references to disabled blocks', () => {
|
||||
// Add connection from disabled block to test block so it's accessible
|
||||
sampleWorkflow.connections.push({ source: 'disabled-block', target: 'test-block' })
|
||||
|
||||
// Make sure disabled block stays disabled and add it to active path for validation
|
||||
const disabledBlock = sampleWorkflow.blocks.find((b) => b.id === 'disabled-block')!
|
||||
disabledBlock.enabled = false
|
||||
mockContext.activeExecutionPath.add('disabled-block')
|
||||
@@ -421,14 +406,14 @@ describe('InputResolver', () => {
|
||||
it('should resolve environment variables in API key contexts', () => {
|
||||
const block: SerializedBlock = {
|
||||
id: 'test-block',
|
||||
metadata: { id: BlockType.API, name: 'Test API Block' }, // API block type
|
||||
metadata: { id: BlockType.API, name: 'Test API Block' },
|
||||
position: { x: 0, y: 0 },
|
||||
config: {
|
||||
tool: 'api',
|
||||
params: {
|
||||
apiKey: '{{API_KEY}}',
|
||||
url: 'https://example.com?key={{API_KEY}}',
|
||||
regularParam: 'Base URL is: {{BASE_URL}}', // Should not be resolved in regular params
|
||||
regularParam: 'Base URL is: {{BASE_URL}}',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
@@ -444,7 +429,7 @@ describe('InputResolver', () => {
|
||||
|
||||
expect(result.apiKey).toBe('test-api-key')
|
||||
expect(result.url).toBe('https://example.com?key=test-api-key')
|
||||
expect(result.regularParam).toBe('Base URL is: {{BASE_URL}}') // Should not be resolved
|
||||
expect(result.regularParam).toBe('Base URL is: {{BASE_URL}}')
|
||||
})
|
||||
|
||||
it('should resolve explicit environment variables', () => {
|
||||
@@ -455,7 +440,7 @@ describe('InputResolver', () => {
|
||||
config: {
|
||||
tool: 'generic',
|
||||
params: {
|
||||
explicitEnv: '{{BASE_URL}}', // Full string is just an env var
|
||||
explicitEnv: '{{BASE_URL}}',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
@@ -490,7 +475,6 @@ describe('InputResolver', () => {
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
// Environment variable should not be resolved in regular contexts
|
||||
expect(result.regularParam).toBe('Value with {{API_KEY}} embedded')
|
||||
})
|
||||
})
|
||||
@@ -538,8 +522,8 @@ describe('InputResolver', () => {
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.tableParam[0].cells.Value).toBe('Hello') // string var
|
||||
expect(result.tableParam[1].cells.Value).toBe(42) // number var - correctly typed
|
||||
expect(result.tableParam[0].cells.Value).toBe('Hello')
|
||||
expect(result.tableParam[1].cells.Value).toBe(42)
|
||||
expect(result.tableParam[2].cells.Value).toBe('Raw text without quotes') // plain var
|
||||
})
|
||||
|
||||
@@ -579,7 +563,7 @@ describe('InputResolver', () => {
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.tableParam[0].cells.Value).toBe('Hello World')
|
||||
expect(result.tableParam[1].cells.Value).toBe('42') // Result values come as strings
|
||||
expect(result.tableParam[1].cells.Value).toBe('42')
|
||||
})
|
||||
|
||||
it('should handle interpolated variable references in table cells', () => {
|
||||
@@ -635,9 +619,7 @@ describe('InputResolver', () => {
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
// String should be quoted in code context
|
||||
expect(result.code).toContain('const name = "Hello";')
|
||||
// Number should not be quoted
|
||||
expect(result.code).toContain('const num = 42;')
|
||||
})
|
||||
|
||||
@@ -661,7 +643,6 @@ describe('InputResolver', () => {
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
// Body should be parsed into an object
|
||||
expect(result.body).toEqual({
|
||||
name: 'Hello',
|
||||
value: 42,
|
||||
@@ -688,7 +669,6 @@ describe('InputResolver', () => {
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
// Conditions should be passed through without parsing for condition blocks
|
||||
expect(result.conditions).toBe('<start.input> === "Hello World"')
|
||||
})
|
||||
})
|
||||
@@ -739,7 +719,7 @@ describe('InputResolver', () => {
|
||||
config: {
|
||||
tool: BlockType.FUNCTION,
|
||||
params: {
|
||||
item: '<loop.currentItem>', // Direct reference, not wrapped in quotes
|
||||
item: '<loop.currentItem>',
|
||||
},
|
||||
},
|
||||
inputs: {},
|
||||
@@ -801,7 +781,7 @@ describe('InputResolver', () => {
|
||||
config: {
|
||||
tool: BlockType.FUNCTION,
|
||||
params: {
|
||||
index: '<loop.index>', // Direct reference, not wrapped in quotes
|
||||
index: '<loop.index>',
|
||||
},
|
||||
},
|
||||
inputs: {},
|
||||
@@ -2387,4 +2367,212 @@ describe('InputResolver', () => {
|
||||
expect(result3).not.toHaveProperty('content')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Variable Reference Validation', () => {
|
||||
it('should allow block references without dots like <start>', () => {
|
||||
const block: SerializedBlock = {
|
||||
id: 'test-block',
|
||||
metadata: { id: 'generic', name: 'Test Block' },
|
||||
position: { x: 0, y: 0 },
|
||||
config: {
|
||||
tool: 'generic',
|
||||
params: {
|
||||
content: 'Value from <start> block',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
content: 'string',
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.content).not.toBe('Value from <start> block')
|
||||
})
|
||||
|
||||
it('should allow other block references without dots', () => {
|
||||
const testAccessibility = new Map<string, Set<string>>()
|
||||
const allIds = [
|
||||
'starter-block',
|
||||
'function-block',
|
||||
'condition-block',
|
||||
'api-block',
|
||||
'testblock',
|
||||
]
|
||||
allIds.forEach((id) => {
|
||||
testAccessibility.set(id, new Set(allIds))
|
||||
})
|
||||
testAccessibility.set('test-block', new Set(allIds))
|
||||
|
||||
const testResolver = new InputResolver(
|
||||
sampleWorkflow,
|
||||
mockEnvironmentVars,
|
||||
mockWorkflowVars,
|
||||
undefined,
|
||||
testAccessibility
|
||||
)
|
||||
|
||||
const extendedWorkflow = {
|
||||
...sampleWorkflow,
|
||||
blocks: [
|
||||
...sampleWorkflow.blocks,
|
||||
{
|
||||
id: 'testblock',
|
||||
metadata: { id: 'generic', name: 'TestBlock' },
|
||||
position: { x: 500, y: 100 },
|
||||
config: { tool: 'generic', params: {} },
|
||||
inputs: {},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const extendedContext = {
|
||||
...mockContext,
|
||||
workflow: extendedWorkflow,
|
||||
blockStates: new Map([
|
||||
...mockContext.blockStates,
|
||||
['testblock', { output: { result: 'test result' } }],
|
||||
]),
|
||||
activeExecutionPath: new Set([...mockContext.activeExecutionPath, 'testblock']),
|
||||
}
|
||||
|
||||
const testResolverWithExtended = new InputResolver(
|
||||
extendedWorkflow,
|
||||
mockEnvironmentVars,
|
||||
mockWorkflowVars,
|
||||
undefined,
|
||||
testAccessibility
|
||||
)
|
||||
|
||||
const block: SerializedBlock = {
|
||||
id: 'test-block',
|
||||
metadata: { id: 'generic', name: 'Test Block' },
|
||||
position: { x: 0, y: 0 },
|
||||
config: {
|
||||
tool: 'generic',
|
||||
params: {
|
||||
content: 'Value from <testblock> is here',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
content: 'string',
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
expect(() => testResolverWithExtended.resolveInputs(block, extendedContext)).not.toThrow()
|
||||
})
|
||||
|
||||
it('should reject operator expressions that look like comparisons', () => {
|
||||
const block: SerializedBlock = {
|
||||
id: 'condition-block',
|
||||
metadata: { id: BlockType.CONDITION, name: 'Condition Block' },
|
||||
position: { x: 0, y: 0 },
|
||||
config: {
|
||||
tool: 'condition',
|
||||
params: {
|
||||
conditions: 'x < 5 && 8 > b',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
conditions: 'string',
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.conditions).toBe('x < 5 && 8 > b')
|
||||
})
|
||||
|
||||
it('should still allow regular dotted references', () => {
|
||||
const block: SerializedBlock = {
|
||||
id: 'test-block',
|
||||
metadata: { id: 'generic', name: 'Test Block' },
|
||||
position: { x: 0, y: 0 },
|
||||
config: {
|
||||
tool: 'generic',
|
||||
params: {
|
||||
starterInput: '<start.input>',
|
||||
functionResult: '<function-block.result>',
|
||||
variableRef: '<variable.stringVar>',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
starterInput: 'string',
|
||||
functionResult: 'string',
|
||||
variableRef: 'string',
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.starterInput).toBe('Hello World')
|
||||
expect(result.functionResult).toBe('42')
|
||||
expect(result.variableRef).toBe('Hello')
|
||||
})
|
||||
|
||||
it('should handle complex expressions with both valid references and operators', () => {
|
||||
const block: SerializedBlock = {
|
||||
id: 'condition-block',
|
||||
metadata: { id: BlockType.CONDITION, name: 'Condition Block' },
|
||||
position: { x: 0, y: 0 },
|
||||
config: {
|
||||
tool: 'condition',
|
||||
params: {
|
||||
conditions:
|
||||
'<start.input> === "Hello" && x < 5 && 8 > y && <function-block.result> !== null',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
conditions: 'string',
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.conditions).toBe(
|
||||
'<start.input> === "Hello" && x < 5 && 8 > y && <function-block.result> !== null'
|
||||
)
|
||||
})
|
||||
|
||||
it('should reject numeric patterns that look like arithmetic', () => {
|
||||
const block: SerializedBlock = {
|
||||
id: 'test-block',
|
||||
metadata: { id: 'generic', name: 'Test Block' },
|
||||
position: { x: 0, y: 0 },
|
||||
config: {
|
||||
tool: 'generic',
|
||||
params: {
|
||||
content1: 'value < 5 is true',
|
||||
content2: 'check 8 > x condition',
|
||||
content3: 'result = 10 + 5',
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
content1: 'string',
|
||||
content2: 'string',
|
||||
content3: 'string',
|
||||
},
|
||||
outputs: {},
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
const result = resolver.resolveInputs(block, mockContext)
|
||||
|
||||
expect(result.content1).toBe('value < 5 is true')
|
||||
expect(result.content2).toBe('check 8 > x condition')
|
||||
expect(result.content3).toBe('result = 10 + 5')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -836,31 +836,11 @@ export class InputResolver {
|
||||
private isValidVariableReference(match: string): boolean {
|
||||
const innerContent = match.slice(1, -1)
|
||||
|
||||
if (!innerContent.includes('.')) {
|
||||
return false
|
||||
}
|
||||
|
||||
const dotIndex = innerContent.indexOf('.')
|
||||
const beforeDot = innerContent.substring(0, dotIndex)
|
||||
const afterDot = innerContent.substring(dotIndex + 1)
|
||||
|
||||
if (afterDot.includes(' ')) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
beforeDot.match(/^\s*[<>=!]+\s*$/) ||
|
||||
beforeDot.match(/\s[<>=!]+\s/) ||
|
||||
beforeDot.match(/^[<>=!]+\s/)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (innerContent.startsWith(' ')) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (innerContent.match(/^[a-zA-Z][a-zA-Z0-9]*$/) && !innerContent.includes('.')) {
|
||||
if (innerContent.match(/^\s*[<>=!]+\s*$/) || innerContent.match(/\s[<>=!]+\s/)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -868,12 +848,26 @@ export class InputResolver {
|
||||
return false
|
||||
}
|
||||
|
||||
if (beforeDot.match(/[+*/=<>!]/)) {
|
||||
return false
|
||||
}
|
||||
if (innerContent.includes('.')) {
|
||||
const dotIndex = innerContent.indexOf('.')
|
||||
const beforeDot = innerContent.substring(0, dotIndex)
|
||||
const afterDot = innerContent.substring(dotIndex + 1)
|
||||
|
||||
if (afterDot.match(/[+\-*/=<>!]/)) {
|
||||
return false
|
||||
if (afterDot.includes(' ')) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (beforeDot.match(/[+*/=<>!]/) || afterDot.match(/[+\-*/=<>!]/)) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
innerContent.match(/[+\-*/=<>!]/) ||
|
||||
innerContent.match(/^\d/) ||
|
||||
innerContent.match(/\s\d/)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user