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
14 changes: 6 additions & 8 deletions apps/sim/blocks/blocks/azure_devops.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { describe, expect, it } from 'vitest'
import { AzureDevOpsBlock } from '@/blocks/blocks/azure_devops'
import type { BlockConfig } from '@/blocks/types'
import type { BlockToolParamsContext } 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
}
/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */
const buildParams = (
inputs: Record<string, unknown>,
context?: BlockToolParamsContext
): Record<string, unknown> => AzureDevOpsBlock.tools.config?.params?.(inputs, context) ?? {}

/**
* 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 }) }
Expand Down
22 changes: 12 additions & 10 deletions apps/sim/blocks/blocks/azure_devops.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AzureIcon } from '@/components/icons'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { withSavedCredential } from '@/blocks/utils'
import { toolCallArg, withSavedCredential } from '@/blocks/utils'
import type { AzureDevOpsBasicWorkItemType } from '@/tools/azure_devops/types'
import { AZURE_DEVOPS_BASIC_WORK_ITEM_STATES } from '@/tools/azure_devops/utils'
import { getTrigger } from '@/triggers'
Expand Down Expand Up @@ -730,8 +730,8 @@ export const AzureDevOpsBlock: BlockConfig = {
...base,
top: resolveTop(params.pipelinesTop, params.top),
continuationToken:
((params.pipelinesContinuationToken ||
(context?.operation ? params.continuationToken : undefined)) as string) ||
(params.pipelinesContinuationToken as string) ||
toolCallArg(context, params.continuationToken) ||
undefined,
}
case 'azure_devops_get_pipeline':
Expand Down Expand Up @@ -777,7 +777,7 @@ export const AzureDevOpsBlock: BlockConfig = {
return {
...base,
wiqlQuery: params.wiqlQuery as string,
top: resolveTop(params.queryTop, context?.operation ? params.top : undefined),
top: resolveTop(params.queryTop, toolCallArg(context, params.top)),
}
case 'azure_devops_get_work_item':
return { ...base, workItemId: Number(params.workItemId) }
Expand Down Expand Up @@ -818,8 +818,9 @@ export const AzureDevOpsBlock: BlockConfig = {
description: (params.description as string) || undefined,
areaPath: (params.areaPath as string) || undefined,
iterationPath:
((params.updateIterationPath ||
(context?.operation ? params.iterationPath : undefined)) as string) || undefined,
(params.updateIterationPath as string) ||
toolCallArg(context, params.iterationPath) ||
undefined,
tags: (params.tags as string) || undefined,
}
case 'azure_devops_add_comment':
Expand All @@ -828,17 +829,18 @@ export const AzureDevOpsBlock: BlockConfig = {
workItemId: Number(params.workItemId),
text: params.commentText as string,
format:
((params.commentFormat ||
(context?.operation ? params.format : undefined)) as string) || undefined,
(params.commentFormat as string) ||
toolCallArg(context, params.format) ||
undefined,
}
case 'azure_devops_get_comments':
return {
...base,
workItemId: Number(params.workItemId),
top: resolveTop(params.commentsTop, params.top),
continuationToken:
((params.commentsContinuationToken ||
(context?.operation ? params.continuationToken : undefined)) as string) ||
(params.commentsContinuationToken as string) ||
toolCallArg(context, params.continuationToken) ||
undefined,
}
default:
Expand Down
14 changes: 6 additions & 8 deletions apps/sim/blocks/blocks/gmail.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { describe, expect, it } from 'vitest'
import { GmailV2Block } from '@/blocks/blocks/gmail'
import type { BlockConfig } from '@/blocks/types'
import type { BlockToolParamsContext } 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
}
/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */
const buildParams = (
inputs: Record<string, unknown>,
context?: BlockToolParamsContext
): Record<string, unknown> => GmailV2Block.tools.config?.params?.(inputs, context) ?? {}

/**
* 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 }) }
Expand Down
14 changes: 7 additions & 7 deletions apps/sim/blocks/blocks/gmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import {
createVersionedToolSelector,
isToolCall,
normalizeFileInput,
SERVICE_ACCOUNT_SUBBLOCKS,
toolCallArg,
} from '@/blocks/utils'
import { getTrigger } from '@/triggers'

Expand Down Expand Up @@ -971,8 +973,6 @@ export const GmailV2Block: BlockConfig = {
}),
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 @@ -984,17 +984,17 @@ export const GmailV2Block: BlockConfig = {
...rest
} = mapped
if (LABEL_WRITE_OPERATIONS.includes(rest.operation)) {
rest.name = labelName || agentArg(rest.name) || undefined
rest.name = labelName || toolCallArg(context, rest.name) || undefined
rest.messageListVisibility = messageListVisibility || undefined
rest.labelListVisibility = labelListVisibility || undefined
}
if (THREAD_TARGET_OPERATIONS.includes(rest.operation)) {
rest.threadId = context?.operation ? targetThreadId || rest.threadId : targetThreadId
rest.format = threadFormat || agentArg(rest.format) || undefined
rest.threadId = isToolCall(context) ? targetThreadId || rest.threadId : targetThreadId
rest.format = threadFormat || toolCallArg(context, rest.format) || undefined
}
if (LIST_OPERATIONS.includes(rest.operation)) {
rest.query = listQuery || agentArg(rest.query) || undefined
const labelIds = threadLabelIds || agentArg(rest.labelIds)
rest.query = listQuery || toolCallArg(context, rest.query) || undefined
const labelIds = threadLabelIds || toolCallArg(context, rest.labelIds)
rest.labelIds = labelIds ? String(labelIds).trim() : undefined
}
return rest
Expand Down
15 changes: 6 additions & 9 deletions apps/sim/blocks/blocks/google_calendar.test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { describe, expect, it } from 'vitest'
import { GoogleCalendarBlock } from '@/blocks/blocks/google_calendar'
import type { BlockConfig } from '@/blocks/types'
import type { BlockToolParamsContext } 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
}
/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */
const buildParams = (
inputs: Record<string, unknown>,
context?: BlockToolParamsContext
): Record<string, unknown> => GoogleCalendarBlock.tools.config?.params?.(inputs, context) ?? {}

/**
* 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' })
Expand Down
6 changes: 2 additions & 4 deletions apps/sim/blocks/blocks/google_calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { GoogleCalendarIcon } from '@/components/icons'
import { getScopesForService } from '@/lib/oauth/utils'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { createVersionedToolSelector, SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils'
import { createVersionedToolSelector, SERVICE_ACCOUNT_SUBBLOCKS, toolCallArg } from '@/blocks/utils'
import { getTrigger } from '@/triggers'

const CALENDAR_FIELD = ['calendarId', 'manualCalendarId'] as const
Expand Down Expand Up @@ -1075,9 +1075,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
processedParams.timeZone =
typeof listTimeZone === 'string' && listTimeZone.trim()
? listTimeZone.trim()
: context?.operation
? processedParams.timeZone
: undefined
: toolCallArg(context, processedParams.timeZone)
}

return {
Expand Down
54 changes: 19 additions & 35 deletions apps/sim/blocks/blocks/google_drive.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { triggersMock } from '@sim/testing/mocks/triggers.mock'
import { describe, expect, it, vi } from 'vitest'
import type { BlockConfig } from '@/blocks/types'
import type { BlockToolParamsContext } 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
}
/** The block's `tools.config.params` mapper; `context` is set on a tool call, absent on the canvas. */
const buildParams = (
inputs: Record<string, unknown>,
context?: BlockToolParamsContext
): Record<string, unknown> => GoogleDriveBlock.tools.config?.params?.(inputs, context) ?? {}

const paginationCases = [
{ operation: 'list', subBlockId: 'pageToken' },
Expand All @@ -22,26 +21,22 @@ const paginationCases = [
] as const

describe('GoogleDriveBlock pagination', () => {
const buildParams = requireParamsMapper(GoogleDriveBlock)

describe.each(paginationCases)('$operation', ({ operation, subBlockId }) => {
/**
* `pageToken` is the canonical tool param, so the `list` case would forward
* through `...rest` even without the mapper. The per-operation ids are the
* ones the mapper has to translate, and none of them may survive as-is.
*/
it('forwards the page token to the tool under its own id', () => {
const params = buildParams({ operation, [subBlockId]: 'token-abc' }, undefined as never)
const params = buildParams({ operation, [subBlockId]: 'token-abc' })

expect(params).toMatchObject({ pageToken: 'token-abc' })
if (subBlockId !== 'pageToken') expect(params[subBlockId]).toBeUndefined()
})
})

it('does not leak a page token into operations that do not paginate', () => {
expect(
buildParams({ operation: 'get_file', pageToken: 'token-abc' }, undefined as never).pageToken
).toBeUndefined()
expect(buildParams({ operation: 'get_file', pageToken: 'token-abc' }).pageToken).toBeUndefined()
})

/**
Expand All @@ -54,7 +49,7 @@ describe('GoogleDriveBlock pagination', () => {
'$subBlockId left over from a previous operation',
({ subBlockId }) => {
it.each(['upload', 'get_file', 'list'])('is dropped under %s', (operation) => {
const params = buildParams({ operation, [subBlockId]: 'stale' }, undefined as never)
const params = buildParams({ operation, [subBlockId]: 'stale' })

expect(params.pageToken).toBeUndefined()
expect(params[subBlockId]).toBeUndefined()
Expand All @@ -63,15 +58,12 @@ describe('GoogleDriveBlock pagination', () => {
)

it('prefers the operation-owned token when a stale sibling is also present', () => {
const params = buildParams(
{
operation: 'search',
searchPageToken: 'search-token',
commentsPageToken: 'stale',
pageToken: 'stale-canonical',
},
undefined as never
)
const params = buildParams({
operation: 'search',
searchPageToken: 'search-token',
commentsPageToken: 'stale',
pageToken: 'stale-canonical',
})

expect(params.pageToken).toBe('search-token')
expect(params.commentsPageToken).toBeUndefined()
Expand All @@ -84,7 +76,6 @@ describe('GoogleDriveBlock pagination', () => {
* 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 }) }
Expand All @@ -107,16 +98,9 @@ describe('GoogleDriveBlock agent tool calls', () => {
expect(buildParams({ operation: 'search', pageToken: 'stale' }).pageToken).toBeUndefined()
})

it('drops a list page token left over on the canvas under a non-paginating operation', () => {
expect(buildParams({ operation: 'get_file', pageToken: 'stale' }).pageToken).toBeUndefined()
})

/** A stored agent tool keeps its operation on the tool, not in the args the mapper receives. */
it.each(['list', 'search', 'list_permissions', 'list_revisions', 'list_comments'])(
'keeps the model pageToken on %s when the args carry no operation',
(operation) => {
const args = { fileId: 'f-1', pageToken: 'next' }
expect({ ...args, ...buildParams(args, { operation }) }.pageToken).toBe('next')
}
)
it('keeps the model pageToken when the args carry no operation', () => {
const args = { fileId: 'f-1', pageToken: 'next' }
expect({ ...args, ...buildParams(args, { operation: 'list_comments' }) }.pageToken).toBe('next')
})
})
16 changes: 10 additions & 6 deletions apps/sim/blocks/blocks/google_drive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { GoogleDriveIcon } from '@/components/icons'
import { getScopesForService } from '@/lib/oauth/utils'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { normalizeFileInput, SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils'
import {
isToolCall,
normalizeFileInput,
SERVICE_ACCOUNT_SUBBLOCKS,
toolCallArg,
} from '@/blocks/utils'
import { getTrigger } from '@/triggers'

/**
Expand Down Expand Up @@ -1685,8 +1690,8 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.`
includeDeleted === 'true' ? true : includeDeleted === 'false' ? false : undefined

let effectivePageSize: string | undefined = pageSize
// 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
// `pageSize`, `pageToken` and `query` are List's fields; other operations trust them only from a tool call
const agentPageSize = toolCallArg(context, pageSize)
if (params.operation === 'search') effectivePageSize = searchPageSize || agentPageSize
else if (params.operation === 'list_revisions')
effectivePageSize = revisionsPageSize || agentPageSize
Expand All @@ -1695,7 +1700,7 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.`
else if (params.operation === 'list_permissions')
effectivePageSize = permissionsPageSize || agentPageSize

const agentPageToken = context?.operation ? pageToken : undefined
const agentPageToken = toolCallArg(context, pageToken)
let effectivePageToken: string | undefined = pageToken
if (params.operation === 'search') effectivePageToken = searchPageToken || agentPageToken
else if (params.operation === 'list_permissions')
Expand All @@ -1704,12 +1709,11 @@ Return ONLY the comment text - no explanations, no quotes, no extra formatting.`
effectivePageToken = revisionsPageToken || agentPageToken
else if (params.operation === 'list_comments')
effectivePageToken = commentsPageToken || agentPageToken
// The canvas list token is stale for every other operation; an agent's own token is not
else if (params.operation !== 'list') effectivePageToken = agentPageToken

const effectiveQuery =
params.operation === 'search'
? context?.operation && !searchQuery
? isToolCall(context) && !searchQuery
? query
: searchQuery
: query
Expand Down
6 changes: 2 additions & 4 deletions apps/sim/blocks/blocks/google_groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { GoogleGroupsIcon } from '@/components/icons'
import { getScopesForService } from '@/lib/oauth/utils'
import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { SERVICE_ACCOUNT_SUBBLOCKS } from '@/blocks/utils'
import { SERVICE_ACCOUNT_SUBBLOCKS, toolCallArg } from '@/blocks/utils'

/** Settings the Update Settings operation forwards when set; empty means unchanged. */
const UPDATE_SETTINGS_FIELDS = [
Expand Down Expand Up @@ -739,9 +739,7 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`,
maxResults: rest.maxResults ? Number(rest.maxResults) : undefined,
pageToken: rest.pageToken || undefined,
userKey: rest.userKey || undefined,
orderBy:
(context?.operation ? rest.orderBy : undefined) ||
(rest.sortOrder ? 'email' : undefined),
orderBy: toolCallArg(context, rest.orderBy) || (rest.sortOrder ? 'email' : undefined),
sortOrder: rest.sortOrder || undefined,
}
case 'get_group':
Expand Down
Loading
Loading