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
2 changes: 2 additions & 0 deletions apps/sim/executor/execution/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@ export class DAGExecutor {
completedLoops: snapshotState?.completedLoops
? new Set(snapshotState.completedLoops)
: new Set(),
// Deliberately not restored from a snapshot: it is a cache, so a resumed run re-resolves.
toolBindingLabelCache: new Map(),
loopExecutions: snapshotState?.loopExecutions
? new Map(
Object.entries(snapshotState.loopExecutions).map(([loopId, scope]) => [
Expand Down
38 changes: 17 additions & 21 deletions apps/sim/executor/handlers/agent/agent-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -934,13 +934,13 @@ describe('AgentBlockHandler', () => {
expect(tools.length).toBe(2)

const autoTool = tools.find(
(t: { name?: string; id?: string; usageControl?: string }) => t.name === 'auto_tool'
(t: { id?: string; usageControl?: string }) => t.id === 'custom_Auto Tool'
)
const forceTool = tools.find(
(t: { name?: string; id?: string; usageControl?: string }) => t.name === 'force_tool'
(t: { id?: string; usageControl?: string }) => t.id === 'custom_Force Tool'
)
const noneTool = tools.find(
(t: { name?: string; id?: string; usageControl?: string }) => t.name === 'none_tool'
(t: { id?: string; usageControl?: string }) => t.id === 'custom_None Tool'
)

expect(autoTool).toBeDefined()
Expand Down Expand Up @@ -1102,18 +1102,16 @@ describe('AgentBlockHandler', () => {

expect(requestBody.tools.length).toBe(2)

const toolNames = requestBody.tools.map(
(t: { name?: string; id?: string; usageControl?: string }) => t.name
)
expect(toolNames).toContain('custom_tool_auto')
expect(toolNames).toContain('custom_tool_force')
expect(toolNames).not.toContain('custom_tool_none')
const toolNames = requestBody.tools.map((t: { id?: string; usageControl?: string }) => t.id)
expect(toolNames).toContain('custom_Custom Tool - Auto')
expect(toolNames).toContain('custom_Custom Tool - Force')
expect(toolNames).not.toContain('custom_Custom Tool - None')

const autoTool = requestBody.tools.find(
(t: { name?: string; id?: string; usageControl?: string }) => t.name === 'custom_tool_auto'
(t: { id?: string; usageControl?: string }) => t.id === 'custom_Custom Tool - Auto'
)
const forceTool = requestBody.tools.find(
(t: { name?: string; id?: string; usageControl?: string }) => t.name === 'custom_tool_force'
(t: { id?: string; usageControl?: string }) => t.id === 'custom_Custom Tool - Force'
)

expect(autoTool.usageControl).toBe('auto')
Expand Down Expand Up @@ -1653,7 +1651,7 @@ describe('AgentBlockHandler', () => {
}),
}),
expect.objectContaining({
name: 'search_files',
id: expect.stringContaining('search_files'),
description: 'MCP tool search_files from Docs {{MCP_SERVER_LABEL}}',
parameters: expect.objectContaining({
properties: {
Expand Down Expand Up @@ -3399,7 +3397,7 @@ describe('AgentBlockHandler', () => {
const providerCallArgs = mockExecuteProviderRequest.mock.calls[0]
expect(providerCallArgs[1].tools).toBeDefined()
expect(providerCallArgs[1].tools.length).toBe(1)
expect(providerCallArgs[1].tools[0].name).toBe('search_files')
expect(providerCallArgs[1].tools[0].id).toContain('search_files')
})

it('should pass callChain to executeProviderRequest for MCP cycle detection', async () => {
Expand Down Expand Up @@ -3853,7 +3851,7 @@ describe('AgentBlockHandler', () => {
const tools = providerCall[1].tools

expect(tools.length).toBe(1)
expect(tools[0].name).toBe('formatReport')
expect(tools[0].id).toBe('custom_formatReport')
expect(tools[0].parameters.required).toContain('format')
})

Expand Down Expand Up @@ -3889,7 +3887,7 @@ describe('AgentBlockHandler', () => {
expect(mockGetCustomToolById).toHaveBeenCalledWith(expect.objectContaining({ toolId }))
const providerRequest = mockExecuteProviderRequest.mock.calls[0][1]
expect(providerRequest.tools).toHaveLength(1)
expect(providerRequest.tools[0].name).toBe('formatReport')
expect(providerRequest.tools[0].id).toBe('custom_formatReport')
expect(JSON.stringify(providerRequest.tools)).not.toContain(toolId)
expect(JSON.stringify(providerRequest.tools)).not.toContain('CANARY_CUSTOM_TOOL_ID')
expect(inputs.tools[0].customToolId).toBe('{{CANARY_CUSTOM_TOOL_ID}}')
Expand Down Expand Up @@ -4025,9 +4023,7 @@ describe('AgentBlockHandler', () => {
await handler.execute(mockContext, mockBlock, inputs)

const providerRequest = mockExecuteProviderRequest.mock.calls[0][1]
expect(providerRequest.tools).toContainEqual(
expect.objectContaining({ id: 'load_skill', name: 'load_skill' })
)
expect(providerRequest.tools).toContainEqual(expect.objectContaining({ id: 'load_skill' }))
expect(JSON.stringify(providerRequest.tools)).toContain('Reporting')
expect(inputs.skills[0].skillId).toBe('{{CANARY_SKILL_ID}}')
expect(mockContext.resolvedSecretTraceRegistry?.getActiveMatches()).toEqual([])
Expand Down Expand Up @@ -4061,7 +4057,7 @@ describe('AgentBlockHandler', () => {
const tools = providerCall[1].tools

expect(tools.length).toBe(1)
expect(tools[0].name).toBe('formatReport')
expect(tools[0].id).toBe('custom_formatReport')
expect(tools[0].parameters.required).not.toContain('format')
})

Expand Down Expand Up @@ -4121,7 +4117,7 @@ describe('AgentBlockHandler', () => {
const tools = providerCall[1].tools

expect(tools.length).toBe(1)
expect(tools[0].name).toBe('formatReport')
expect(tools[0].id).toBe('custom_formatReport')
})

it('should not fetch from DB when no customToolId is present', async () => {
Expand Down Expand Up @@ -4151,7 +4147,7 @@ describe('AgentBlockHandler', () => {
const tools = providerCall[1].tools

expect(tools.length).toBe(1)
expect(tools[0].name).toBe('formatReport')
expect(tools[0].id).toBe('custom_formatReport')
expect(tools[0].parameters.required).not.toContain('format')
})
})
Expand Down
14 changes: 6 additions & 8 deletions apps/sim/executor/handlers/agent/agent-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import type {
ResolvedSecretInputPath,
ResolvedSecretTraceRegistry,
} from '@/executor/utils/resolved-secret-trace-registry'
import { annotateDuplicateToolBindings } from '@/executor/utils/tool-binding-labels'
import { resolveVertexCredential } from '@/executor/utils/vertex-credential'
import { executeProviderRequest } from '@/providers'
import {
Expand Down Expand Up @@ -804,12 +805,11 @@ export class AgentBlockHandler implements BlockHandler {
)

const allTools = [...otherResults, ...mcpResults]
return {
tools: allTools.filter(
(tool): tool is ProviderToolConfig => tool !== null && tool !== undefined
),
inputProvenance,
}
const tools = allTools.filter(
(tool): tool is ProviderToolConfig => tool !== null && tool !== undefined
)
await annotateDuplicateToolBindings(ctx, tools)
return { tools, inputProvenance }
}

private assertInputPathsDoNotResolveSecrets(
Expand Down Expand Up @@ -965,7 +965,6 @@ export class AgentBlockHandler implements BlockHandler {
const toolId = `${AGENT.CUSTOM_TOOL_PREFIX}${title}`
const base: any = {
id: toolId,
name: schema.function.name,
description: projectedDescription || '',
params: userProvidedParams,
parameters: {
Expand Down Expand Up @@ -1377,7 +1376,6 @@ export class AgentBlockHandler implements BlockHandler {

return {
id: toolId,
name: config.toolName,
description: config.description,
parameters: filteredSchema,
params: config.userProvidedParams,
Expand Down
1 change: 0 additions & 1 deletion apps/sim/executor/handlers/agent/skills-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ export function buildSkillsSystemPromptSection(skills: SkillMetadata[]): string
export function buildLoadSkillTool(skillNames: string[]) {
return {
id: 'load_skill',
name: 'load_skill',
description: `Load a skill to get specialized instructions. Available skills: ${skillNames.join(', ')}`,
params: {},
parameters: {
Expand Down
5 changes: 4 additions & 1 deletion apps/sim/executor/handlers/pi/local/sim-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { PiToolResult, PiToolSpec } from '@/executor/handlers/pi/core/backe
import type { ExecutionContext } from '@/executor/types'
import { projectResolvedSecretModelContent } from '@/executor/utils/resolved-secret-content-projection'
import type { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import { annotateDuplicateToolBindings } from '@/executor/utils/tool-binding-labels'
import { assignProviderToolIdentities } from '@/providers/tool-identity'
import type { ProviderToolConfig } from '@/providers/types'
import { transformBlockTool } from '@/providers/utils'
Expand Down Expand Up @@ -231,7 +232,9 @@ export async function buildSimToolSpecs(
}
}

assignProviderToolIdentities(configuredTools.map(({ provider }) => provider))
const providers = configuredTools.map(({ provider }) => provider)
await annotateDuplicateToolBindings(ctx, providers)
assignProviderToolIdentities(providers)
return configuredTools.map(({ provider, toolIndex }) =>
buildSimToolSpec(ctx, inputTools, provider, toolIndex)
)
Expand Down
10 changes: 10 additions & 0 deletions apps/sim/executor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,16 @@ export interface ExecutionContext {
permissionConfig?: PermissionGroupConfig | null
permissionConfigLoaded?: boolean

/**
* Resolved display names for the resources an agent tool is bound to, keyed `${kind}:${id}`,
* with `null` recording a miss so it is not retried. Shared across the whole run: an agent block
* inside a loop re-formats its tools every iteration, and its bound resources do not change.
*
* A Map rather than plain fields on purpose — `blockCtx` is a shallow clone of this context per
* block execution, so only a shared reference survives; a scalar written here would be lost.
*/
toolBindingLabelCache?: Map<string, string | null>

blockStates: ReadonlyMap<string, BlockState>
executedBlocks: ReadonlySet<string>

Expand Down
Loading
Loading