Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion apps/sim/providers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1011,10 +1011,21 @@ export function prepareToolExecution(
toolParams: Record<string, any>
executionParams: Record<string, any>
} {
// Filter out empty/null/undefined values from user params
// so that cleared fields don't override LLM-generated values
const filteredUserParams: Record<string, any> = {}
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
Expand Down
38 changes: 38 additions & 0 deletions apps/sim/tools/params.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
17 changes: 14 additions & 3 deletions apps/sim/tools/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>,
llmGeneratedParams: Record<string, unknown>
): Record<string, unknown> {
// 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<string, unknown> = {}
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,
}
}

Expand Down