diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index 2a755c3095..ca0b6db77f 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -1011,10 +1011,21 @@ export function prepareToolExecution( toolParams: Record executionParams: Record } { + // Filter out empty/null/undefined values from user params + // so that cleared fields don't override LLM-generated values + const filteredUserParams: Record = {} + if (tool.params) { + for (const [key, value] of Object.entries(tool.params)) { + if (value !== undefined && value !== null && value !== '') { + filteredUserParams[key] = value + } + } + } + // User-provided params take precedence over LLM-generated params const toolParams = { ...llmArgs, - ...tool.params, + ...filteredUserParams, } // Add system parameters for execution diff --git a/apps/sim/tools/params.test.ts b/apps/sim/tools/params.test.ts index c29c098954..c026a4d2d2 100644 --- a/apps/sim/tools/params.test.ts +++ b/apps/sim/tools/params.test.ts @@ -177,6 +177,44 @@ describe('Tool Parameters Utils', () => { expect(merged.message).toBe('Hello world') expect(merged.timeout).toBe(10000) }) + + it.concurrent('should skip empty strings so LLM values are used', () => { + const userProvided = { + apiKey: 'user-key', + channel: '', // User cleared this field + message: '', // User cleared this field too + } + const llmGenerated = { + message: 'Hello world', + channel: '#random', + timeout: 10000, + } + + const merged = mergeToolParameters(userProvided, llmGenerated) + + expect(merged.apiKey).toBe('user-key') // Non-empty user value preserved + expect(merged.channel).toBe('#random') // LLM value used because user value was empty + expect(merged.message).toBe('Hello world') // LLM value used because user value was empty + expect(merged.timeout).toBe(10000) + }) + + it.concurrent('should skip null and undefined values', () => { + const userProvided = { + apiKey: 'user-key', + channel: null, + message: undefined, + } + const llmGenerated = { + message: 'Hello world', + channel: '#random', + } + + const merged = mergeToolParameters(userProvided, llmGenerated) + + expect(merged.apiKey).toBe('user-key') + expect(merged.channel).toBe('#random') // LLM value used + expect(merged.message).toBe('Hello world') // LLM value used + }) }) describe('validateToolParameters', () => { diff --git a/apps/sim/tools/params.ts b/apps/sim/tools/params.ts index 8544d7900a..1230cdc8e0 100644 --- a/apps/sim/tools/params.ts +++ b/apps/sim/tools/params.ts @@ -572,16 +572,27 @@ export function createExecutionToolSchema(toolConfig: ToolConfig): ToolSchema { } /** - * Merges user-provided parameters with LLM-generated parameters + * Merges user-provided parameters with LLM-generated parameters. + * User-provided parameters take precedence, but empty strings are skipped + * so that LLM-generated values are used when user clears a field. */ export function mergeToolParameters( userProvidedParams: Record, llmGeneratedParams: Record ): Record { - // User-provided parameters take precedence + // Filter out empty strings from user-provided params + // so that cleared fields don't override LLM values + const filteredUserParams: Record = {} + for (const [key, value] of Object.entries(userProvidedParams)) { + if (value !== undefined && value !== null && value !== '') { + filteredUserParams[key] = value + } + } + + // User-provided parameters take precedence (after filtering empty values) return { ...llmGeneratedParams, - ...userProvidedParams, + ...filteredUserParams, } }