Skip to content

Commit f5c2023

Browse files
committed
fix(zoho-desk): do not scope params on the agent-tool path
The previous commit made the mapper assign every operation-scoped param unconditionally, so the merge could not resurrect a stale value. That is right on the canvas path and wrong on the agent-tool path, where `operation` is a sibling of the tool call rather than a member of params: the mapper saw `operation === undefined`, every gate resolved to undefined, and the merge then overwrote the model's own arguments with it. A Zoho Desk tool called by an agent lost every parameter the model supplied. That path needs no scoping — the tool is already chosen, and the model addresses tool params by their real names — so it now returns early. Custom fields are still coerced there, since parsing JSON is a type fix rather than an operation gate, and that parsing is now shared by both paths.
1 parent a3d3100 commit f5c2023

2 files changed

Lines changed: 73 additions & 14 deletions

File tree

apps/sim/blocks/blocks/zoho-desk.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ function toPaginationValue(value: unknown, min: number): number | undefined {
4444
return Number.isInteger(parsed) && parsed >= min ? parsed : undefined
4545
}
4646

47+
/**
48+
* Accept the custom-field map as either an object (an agent supplying it
49+
* directly) or the JSON text the subBlock stores. Anything unparseable fails
50+
* loudly rather than reaching Zoho as a string it would silently ignore.
51+
*/
52+
function parseCustomFields(value: unknown): Record<string, unknown> | undefined {
53+
if (value === undefined || value === null) return undefined
54+
if (typeof value !== 'string') return value as Record<string, unknown>
55+
if (!value.trim()) return undefined
56+
try {
57+
return JSON.parse(value)
58+
} catch {
59+
throw new Error('Invalid JSON provided for custom fields')
60+
}
61+
}
62+
4763
export const ZohoDeskBlock: BlockConfig<ZohoDeskResponse> = {
4864
type: 'zoho_desk',
4965
name: 'Zoho Desk',
@@ -524,6 +540,16 @@ export const ZohoDeskBlock: BlockConfig<ZohoDeskResponse> = {
524540
config: {
525541
tool: (params) => `zoho_desk_${params.operation}`,
526542
params: (params) => {
543+
// The agent-tool path does not carry `operation` inside params - it is a
544+
// sibling of the tool call, used only to pick the tool - and there the
545+
// model addresses tool params by their real names. The tool is already
546+
// selected, so there is no cross-operation leak to guard against, while
547+
// running the scoping below WOULD overwrite the model's own values with
548+
// `undefined`. Leave those params alone; only coerce the JSON field,
549+
// which is a type fix rather than an operation gate.
550+
if (typeof params.operation !== 'string') {
551+
return { ...params, customFields: parseCustomFields(params.customFields) }
552+
}
527553
// IMPORTANT: destructuring a key out of `rest` does NOT keep it from the
528554
// tool. Both call sites merge this function's return value on top of the
529555
// original inputs (`{ ...inputs, ...transformedParams }` in
@@ -668,20 +694,8 @@ export const ZohoDeskBlock: BlockConfig<ZohoDeskResponse> = {
668694
const receivedInDays = isListTickets ? orUndefined(rawReceivedInDays) : undefined
669695
result.receivedInDays = receivedInDays === undefined ? undefined : Number(receivedInDays)
670696

671-
if (params.operation === 'update_ticket' && rawCustomFields !== undefined) {
672-
if (typeof rawCustomFields === 'string') {
673-
if (rawCustomFields.trim()) {
674-
try {
675-
result.customFields = JSON.parse(rawCustomFields)
676-
} catch {
677-
throw new Error('Invalid JSON provided for custom fields')
678-
}
679-
}
680-
} else if (rawCustomFields !== null) {
681-
// Already an object when an agent supplies it directly.
682-
result.customFields = rawCustomFields
683-
}
684-
}
697+
result.customFields =
698+
params.operation === 'update_ticket' ? parseCustomFields(rawCustomFields) : undefined
685699
return result
686700
},
687701
},

apps/sim/tools/zoho_desk/list_tickets.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,48 @@ describe('ZohoDeskBlock overwrites stale advanced values from another operation'
180180
expect(() => throughSeam({ operation: 'list_tickets', receivedInDays: '' })).not.toThrow()
181181
})
182182
})
183+
184+
/**
185+
* On the agent-tool path `operation` is a sibling of the tool call rather than a
186+
* member of params, so the mapper cannot scope by it — and must not try. The
187+
* model addresses tool params by their real names, and the tool has already been
188+
* selected, so scoping there would erase the model's own arguments.
189+
*/
190+
describe('ZohoDeskBlock leaves agent-supplied params alone', () => {
191+
const buildParams = ZohoDeskBlock.tools.config?.params
192+
193+
const merged = (llmArgs: Record<string, unknown>) => {
194+
if (!buildParams) throw new Error('ZohoDeskBlock is missing tools.config.params')
195+
return { ...llmArgs, ...(buildParams({ ...llmArgs }) as Record<string, unknown>) }
196+
}
197+
198+
it('preserves every tool param the model supplied', () => {
199+
const result = merged({
200+
status: 'Open',
201+
priority: 'High',
202+
sortBy: '-createdTime',
203+
include: 'contacts',
204+
assignee: 'Unassigned',
205+
channel: 'Email',
206+
receivedInDays: 30,
207+
ticketId: '123',
208+
})
209+
expect(result).toMatchObject({
210+
status: 'Open',
211+
priority: 'High',
212+
sortBy: '-createdTime',
213+
include: 'contacts',
214+
assignee: 'Unassigned',
215+
channel: 'Email',
216+
receivedInDays: 30,
217+
ticketId: '123',
218+
})
219+
})
220+
221+
it('still coerces a JSON custom-field string, which is a type fix not a gate', () => {
222+
expect(merged({ customFields: '{"cf_severity":"High"}' }).customFields).toEqual({
223+
cf_severity: 'High',
224+
})
225+
expect(() => merged({ customFields: '{not json' })).toThrow(/Invalid JSON/)
226+
})
227+
})

0 commit comments

Comments
 (0)