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
63 changes: 63 additions & 0 deletions apps/sim/blocks/blocks/azure_devops.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { describe, expect, it } from 'vitest'
import { AzureDevOpsBlock } from '@/blocks/blocks/azure_devops'
import type { BlockConfig } from '@/blocks/types'

/** The block's `tools.config.params` mapper, which every block under test defines. */
function requireParamsMapper(block: BlockConfig) {
const mapper = block.tools.config?.params
if (!mapper) throw new Error(`${block.type} has no params mapper`)
return mapper
}

/**
* On an agent call the model supplies `continuationToken`/`top`/`iterationPath`/`format` under the
* tool's own names; the per-operation fields (`pipelinesContinuationToken`, `queryTop`, …) are never
* rendered in the agent tool input. The handler forwards `{ ...inputs, ...mapped }`.
*/
describe('AzureDevOpsBlock agent tool calls', () => {
const buildParams = requireParamsMapper(AzureDevOpsBlock)
const agentCall = (operation: string, args: Record<string, unknown>): Record<string, unknown> => {
const inputs = { ...args, operation, organization: 'org', project: 'proj' }
return { ...inputs, ...buildParams(inputs, { operation }) }
}

it.each(['azure_devops_list_pipelines', 'azure_devops_get_comments'])(
'keeps the model continuationToken on %s',
(operation) => {
expect(
agentCall(operation, { workItemId: 1, continuationToken: 'ct' }).continuationToken
).toBe('ct')
}
)

it('keeps the model top on azure_devops_query_work_items', () => {
expect(agentCall('azure_devops_query_work_items', { wiqlQuery: 'q', top: 5 }).top).toBe(5)
})

it('keeps the model iterationPath on azure_devops_update_work_item', () => {
const mapped = agentCall('azure_devops_update_work_item', {
workItemId: 1,
iterationPath: 'proj\\Sprint 2',
})
expect(mapped.iterationPath).toBe('proj\\Sprint 2')
})

it('keeps the model format on azure_devops_add_comment', () => {
const mapped = agentCall('azure_devops_add_comment', { workItemId: 1, format: 'markdown' })
expect(mapped.format).toBe('markdown')
})

it('drops a List Builds token left over on the canvas under list pipelines', () => {
const inputs = { operation: 'azure_devops_list_pipelines', continuationToken: 'stale' }
expect({ ...inputs, ...buildParams(inputs) }.continuationToken).toBeUndefined()
})

it('drops a Create Work Item iteration path left over on the canvas under update', () => {
const inputs = {
operation: 'azure_devops_update_work_item',
workItemId: 1,
iterationPath: 'stale',
}
expect({ ...inputs, ...buildParams(inputs) }.iterationPath).toBeUndefined()
})
})
22 changes: 16 additions & 6 deletions apps/sim/blocks/blocks/azure_devops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ export const AzureDevOpsBlock: BlockConfig = {
],
config: {
tool: (params) => params.operation as string,
params: (params) => {
params: (params, context) => {
const base = {
accessToken: params.accessToken as string,
organization: params.organization as string,
Expand All @@ -729,7 +729,10 @@ export const AzureDevOpsBlock: BlockConfig = {
return {
...base,
top: resolveTop(params.pipelinesTop, params.top),
continuationToken: (params.pipelinesContinuationToken as string) || undefined,
continuationToken:
((params.pipelinesContinuationToken ||
(context?.operation ? params.continuationToken : undefined)) as string) ||
undefined,
}
case 'azure_devops_get_pipeline':
return { ...base, pipelineId: Number(params.pipelineId) }
Expand Down Expand Up @@ -774,7 +777,7 @@ export const AzureDevOpsBlock: BlockConfig = {
return {
...base,
wiqlQuery: params.wiqlQuery as string,
top: params.queryTop ? Number(params.queryTop) : undefined,
top: resolveTop(params.queryTop, context?.operation ? params.top : undefined),
}
case 'azure_devops_get_work_item':
return { ...base, workItemId: Number(params.workItemId) }
Expand Down Expand Up @@ -814,22 +817,29 @@ export const AzureDevOpsBlock: BlockConfig = {
completedWork: params.completedWork ? Number(params.completedWork) : undefined,
description: (params.description as string) || undefined,
areaPath: (params.areaPath as string) || undefined,
iterationPath: (params.updateIterationPath as string) || undefined,
iterationPath:
((params.updateIterationPath ||
(context?.operation ? params.iterationPath : undefined)) as string) || undefined,
tags: (params.tags as string) || undefined,
}
case 'azure_devops_add_comment':
return {
...base,
workItemId: Number(params.workItemId),
text: params.commentText as string,
format: (params.commentFormat as string) || undefined,
format:
((params.commentFormat ||
(context?.operation ? params.format : undefined)) as string) || undefined,
}
case 'azure_devops_get_comments':
return {
...base,
workItemId: Number(params.workItemId),
top: resolveTop(params.commentsTop, params.top),
continuationToken: (params.commentsContinuationToken as string) || undefined,
continuationToken:
((params.commentsContinuationToken ||
(context?.operation ? params.continuationToken : undefined)) as string) ||
undefined,
}
default:
return base
Expand Down
50 changes: 50 additions & 0 deletions apps/sim/blocks/blocks/gmail.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expect, it } from 'vitest'
import { GmailV2Block } from '@/blocks/blocks/gmail'
import type { BlockConfig } from '@/blocks/types'

/** The block's `tools.config.params` mapper, which every block under test defines. */
function requireParamsMapper(block: BlockConfig) {
const mapper = block.tools.config?.params
if (!mapper) throw new Error(`${block.type} has no params mapper`)
return mapper
}

/**
* On an agent call the model supplies `threadId`/`format`/`name`/`query`/`labelIds` under the
* tool's own names; the dedicated V2 fields (`targetThreadId`, `threadFormat`, `labelName`,
* `listQuery`, `threadLabelIds`) are never rendered in the agent tool input.
*/
describe('GmailV2Block agent tool calls', () => {
const buildParams = requireParamsMapper(GmailV2Block)
const agentCall = (operation: string, args: Record<string, unknown>): Record<string, unknown> => {
const inputs = { ...args, operation }
return { ...inputs, ...buildParams(inputs, { operation }) }
}

it('keeps the model threadId and format on get_thread_gmail', () => {
const mapped = agentCall('get_thread_gmail', { threadId: 't-1', format: 'minimal' })
expect(mapped.threadId).toBe('t-1')
expect(mapped.format).toBe('minimal')
})

it.each(['create_label_gmail', 'update_label_gmail'])(
'keeps the model name on %s',
(operation) => {
expect(agentCall(operation, { labelId: 'l-1', name: 'Receipts' }).name).toBe('Receipts')
}
)

it.each(['list_drafts_gmail', 'list_threads_gmail'])(
'keeps the model query and labelIds on %s',
(operation) => {
const mapped = agentCall(operation, { query: 'is:unread', labelIds: 'INBOX' })
expect(mapped.query).toBe('is:unread')
expect(mapped.labelIds).toBe('INBOX')
}
)

it('drops a Send threadId left over on the canvas under get_thread_gmail', () => {
const inputs = { operation: 'get_thread_gmail', threadId: 'stale' }
expect({ ...inputs, ...buildParams(inputs) }.threadId).toBeUndefined()
})
})
17 changes: 10 additions & 7 deletions apps/sim/blocks/blocks/gmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -969,8 +969,10 @@ export const GmailV2Block: BlockConfig = {
suffix: '_v2',
fallbackToolId: 'gmail_send_v2',
}),
params: (params) => {
const mapped = GmailBlock.tools?.config?.params?.(params) ?? params
params: (params, context) => {
const mapped = GmailBlock.tools?.config?.params?.(params, context) ?? params
/** Agent calls carry the model's tool-named args; on the canvas those keys may be stale. */
const agentArg = (value: unknown) => (context?.operation ? value : undefined)
const {
labelName,
targetThreadId,
Expand All @@ -982,17 +984,18 @@ export const GmailV2Block: BlockConfig = {
...rest
} = mapped
if (LABEL_WRITE_OPERATIONS.includes(rest.operation)) {
rest.name = labelName || undefined
rest.name = labelName || agentArg(rest.name) || undefined
rest.messageListVisibility = messageListVisibility || undefined
rest.labelListVisibility = labelListVisibility || undefined
}
if (THREAD_TARGET_OPERATIONS.includes(rest.operation)) {
rest.threadId = targetThreadId
rest.format = threadFormat || undefined
rest.threadId = context?.operation ? targetThreadId || rest.threadId : targetThreadId
rest.format = threadFormat || agentArg(rest.format) || undefined
}
if (LIST_OPERATIONS.includes(rest.operation)) {
rest.query = listQuery || undefined
rest.labelIds = threadLabelIds ? String(threadLabelIds).trim() : undefined
rest.query = listQuery || agentArg(rest.query) || undefined
const labelIds = threadLabelIds || agentArg(rest.labelIds)
rest.labelIds = labelIds ? String(labelIds).trim() : undefined
}
return rest
},
Expand Down
29 changes: 29 additions & 0 deletions apps/sim/blocks/blocks/google_calendar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from 'vitest'
import { GoogleCalendarBlock } from '@/blocks/blocks/google_calendar'
import type { BlockConfig } from '@/blocks/types'

/** The block's `tools.config.params` mapper, which every block under test defines. */
function requireParamsMapper(block: BlockConfig) {
const mapper = block.tools.config?.params
if (!mapper) throw new Error(`${block.type} has no params mapper`)
return mapper
}

/**
* On an agent call the model supplies `timeZone` under the tool's own name; the dedicated
* `listTimeZone` field is never rendered in the agent tool input.
*/
describe('GoogleCalendarBlock list timeZone', () => {
const buildParams = requireParamsMapper(GoogleCalendarBlock)

it('keeps the model timeZone on an agent list call', () => {
const inputs = { operation: 'list', timeZone: 'America/New_York' }
const mapped = buildParams(inputs, { operation: 'list' })
expect({ ...inputs, ...mapped }.timeZone).toBe('America/New_York')
})

it('drops a Create/Update timeZone left over on the canvas under list', () => {
const inputs = { operation: 'list', timeZone: 'America/New_York' }
expect({ ...inputs, ...buildParams(inputs) }.timeZone).toBeUndefined()
})
})
6 changes: 4 additions & 2 deletions apps/sim/blocks/blocks/google_calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
throw new Error(`Invalid Google Calendar operation: ${params.operation}`)
}
},
params: (params) => {
params: (params, context) => {
const {
oauthCredential,
operation,
Expand Down Expand Up @@ -1075,7 +1075,9 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
processedParams.timeZone =
typeof listTimeZone === 'string' && listTimeZone.trim()
? listTimeZone.trim()
: undefined
: context?.operation
? processedParams.timeZone
: undefined
}

return {
Expand Down
39 changes: 38 additions & 1 deletion apps/sim/blocks/blocks/google_drive.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { triggersMock } from '@sim/testing/mocks/triggers.mock'
import { describe, expect, it, vi } from 'vitest'
import type { BlockConfig } from '@/blocks/types'

vi.mock('@/triggers', () => triggersMock)

import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'

/** The block's `tools.config.params` mapper, which every block under test defines. */
function requireParamsMapper(block: BlockConfig) {
const mapper = block.tools.config?.params
if (!mapper) throw new Error(`${block.type} has no params mapper`)
return mapper
}

const paginationCases = [
{ operation: 'list', subBlockId: 'pageToken' },
{ operation: 'search', subBlockId: 'searchPageToken' },
Expand All @@ -14,7 +22,7 @@ const paginationCases = [
] as const

describe('GoogleDriveBlock pagination', () => {
const buildParams = GoogleDriveBlock.tools.config.params!
const buildParams = requireParamsMapper(GoogleDriveBlock)

describe.each(paginationCases)('$operation', ({ operation, subBlockId }) => {
/**
Expand Down Expand Up @@ -70,3 +78,32 @@ describe('GoogleDriveBlock pagination', () => {
expect(params.searchPageToken).toBeUndefined()
})
})

/**
* On an agent call the model supplies `pageSize`/`pageToken`/`query` under the tool's own names;
* the per-operation fields (`permissionsPageSize`, `searchPageToken`, `searchQuery`) are never rendered.
*/
describe('GoogleDriveBlock agent tool calls', () => {
const buildParams = requireParamsMapper(GoogleDriveBlock)
const agentCall = (operation: string, args: Record<string, unknown>): Record<string, unknown> => {
const inputs = { ...args, operation }
return { ...inputs, ...buildParams(inputs, { operation }) }
}

it.each(['search', 'list_permissions', 'list_revisions', 'list_comments'])(
'keeps the model pageSize and pageToken on %s',
(operation) => {
const mapped = agentCall(operation, { fileId: 'f-1', pageSize: 5, pageToken: 'next' })
expect(mapped.pageSize).toBe(5)
expect(mapped.pageToken).toBe('next')
}
)

it('keeps the model query on search', () => {
expect(agentCall('search', { query: 'budget' }).query).toBe('budget')
})

it('drops a list page token left over on the canvas under search', () => {
expect(buildParams({ operation: 'search', pageToken: 'stale' }).pageToken).toBeUndefined()
})
})
34 changes: 24 additions & 10 deletions apps/sim/blocks/blocks/google_drive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1530,7 +1530,7 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.`
throw new Error(`Invalid Google Drive operation: ${params.operation}`)
}
},
params: (params) => {
params: (params, context) => {
const {
oauthCredential,
uploadFolderId,
Expand Down Expand Up @@ -1685,19 +1685,33 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.`
includeDeleted === 'true' ? true : includeDeleted === 'false' ? false : undefined

let effectivePageSize: string | undefined = pageSize
if (params.operation === 'search') effectivePageSize = searchPageSize
else if (params.operation === 'list_revisions') effectivePageSize = revisionsPageSize
else if (params.operation === 'list_comments') effectivePageSize = commentsPageSize
else if (params.operation === 'list_permissions') effectivePageSize = permissionsPageSize
// Basic `pageSize`/`query` and advanced `pageToken` belong to List; only an agent call's value is trusted for other operations
const agentPageSize = context?.operation ? pageSize : undefined
if (params.operation === 'search') effectivePageSize = searchPageSize || agentPageSize
else if (params.operation === 'list_revisions')
effectivePageSize = revisionsPageSize || agentPageSize
else if (params.operation === 'list_comments')
effectivePageSize = commentsPageSize || agentPageSize
else if (params.operation === 'list_permissions')
effectivePageSize = permissionsPageSize || agentPageSize

const agentPageToken = context?.operation ? pageToken : undefined
let effectivePageToken: string | undefined = pageToken
if (params.operation === 'search') effectivePageToken = searchPageToken
else if (params.operation === 'list_permissions') effectivePageToken = permissionsPageToken
else if (params.operation === 'list_revisions') effectivePageToken = revisionsPageToken
else if (params.operation === 'list_comments') effectivePageToken = commentsPageToken
if (params.operation === 'search') effectivePageToken = searchPageToken || agentPageToken
else if (params.operation === 'list_permissions')
effectivePageToken = permissionsPageToken || agentPageToken
else if (params.operation === 'list_revisions')
effectivePageToken = revisionsPageToken || agentPageToken
else if (params.operation === 'list_comments')
effectivePageToken = commentsPageToken || agentPageToken
else if (params.operation !== 'list') effectivePageToken = undefined

const effectiveQuery = params.operation === 'search' ? searchQuery : query
const effectiveQuery =
params.operation === 'search'
? context?.operation && !searchQuery
? query
: searchQuery
: query

const effectiveOrderBy =
params.operation === 'list'
Expand Down
8 changes: 8 additions & 0 deletions apps/sim/blocks/blocks/google_groups.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,11 @@ describe('GoogleGroupsBlock update settings', () => {
expect(mapped.whoCanJoin).toBe('INVITED_CAN_JOIN')
})
})

describe('GoogleGroupsBlock agent tool calls', () => {
it('keeps the model orderBy on list_groups when no sort order is chosen', () => {
const inputs = { operation: 'list_groups', customer: 'my_customer', orderBy: 'email' }
const mapped = GoogleGroupsBlock.tools.config?.params?.(inputs, { operation: 'list_groups' })
expect({ ...inputs, ...mapped }.orderBy).toBe('email')
})
})
Loading
Loading