Skip to content

Commit 5a8e66b

Browse files
committed
Improve tool search
1 parent 0695a8f commit 5a8e66b

12 files changed

Lines changed: 209 additions & 99 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { memo, useMemo } from 'react'
44
import { Read as ReadTool, WorkspaceFile } from '@/lib/copilot/generated/tool-catalog-v1'
5+
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
56
import { resolveToolDisplay } from '@/lib/copilot/tools/client/store-utils'
67
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-call-state'
78
import type { ContentBlock, MothershipResource, OptionItem, ToolCallData } from '../../types'
@@ -17,11 +18,6 @@ import {
1718
} from './components'
1819

1920
const FILE_SUBAGENT_ID = 'file'
20-
const HIDDEN_TOOL_NAMES = new Set([
21-
'tool_search_tool_regex',
22-
'load_agent_skill',
23-
'load_custom_tool',
24-
])
2521

2622
interface TextSegment {
2723
type: 'text'
@@ -80,7 +76,7 @@ function isToolResultRead(params?: Record<string, unknown>): boolean {
8076
}
8177

8278
function isHiddenToolCall(toolName: string | undefined): boolean {
83-
return !!toolName && HIDDEN_TOOL_NAMES.has(toolName)
79+
return isToolHiddenInUi(toolName)
8480
}
8581

8682
function formatToolName(name: string): string {

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ import {
7777
RunWorkflowUntilBlock,
7878
ScrapePage,
7979
SearchOnline,
80-
ToolSearchToolRegex,
8180
WorkspaceFile,
8281
WorkspaceFileOperation,
8382
} from '@/lib/copilot/generated/tool-catalog-v1'
@@ -3268,7 +3267,7 @@ export function useChat(
32683267
const isPartial =
32693268
payload.partial === true ||
32703269
payload.status === MothershipStreamV1ToolStatus.generating
3271-
if (name === ToolSearchToolRegex.id || isToolHiddenInUi(name)) {
3270+
if (isToolHiddenInUi(name)) {
32723271
break
32733272
}
32743273
const ui = getToolUI(payload.ui)

apps/sim/executor/handlers/mothership/mothership-handler.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,59 @@ describe('MothershipBlockHandler', () => {
302302
})
303303
})
304304

305+
it('preserves failed tool calls as output metadata without throwing', async () => {
306+
mockGenerateId.mockReturnValueOnce('chat-uuid')
307+
mockGenerateId.mockReturnValueOnce('message-uuid')
308+
mockGenerateId.mockReturnValueOnce('request-uuid')
309+
310+
fetchMock.mockResolvedValue(
311+
createNdjsonResponse([
312+
{
313+
type: 'final',
314+
data: {
315+
content: 'The lookup failed, so I could not use that result.',
316+
model: 'mothership',
317+
conversationId: 'chat-uuid',
318+
tokens: { total: 7 },
319+
toolCalls: [
320+
{
321+
name: 'lookup_customer',
322+
status: 'error',
323+
params: { email: 'missing@example.com' },
324+
result: { success: false, error: 'Customer not found' },
325+
error: 'Customer not found',
326+
durationMs: 42,
327+
},
328+
],
329+
},
330+
},
331+
])
332+
)
333+
334+
const result = await handler.execute(context, block, { prompt: 'Hello from workflow' })
335+
336+
expect(result).toEqual({
337+
content: 'The lookup failed, so I could not use that result.',
338+
model: 'mothership',
339+
conversationId: 'chat-uuid',
340+
tokens: { total: 7 },
341+
toolCalls: {
342+
list: [
343+
expect.objectContaining({
344+
name: 'lookup_customer',
345+
status: 'error',
346+
arguments: { email: 'missing@example.com' },
347+
result: { success: false, error: 'Customer not found' },
348+
error: 'Customer not found',
349+
duration: 42,
350+
}),
351+
],
352+
count: 1,
353+
},
354+
cost: undefined,
355+
})
356+
})
357+
305358
it('surfaces mothership execute stream errors', async () => {
306359
mockGenerateId.mockReturnValueOnce('chat-uuid')
307360
mockGenerateId.mockReturnValueOnce('message-uuid')

apps/sim/executor/handlers/mothership/mothership-handler.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,9 @@ function formatMothershipBlockOutput(
6363
): NormalizedBlockOutput {
6464
const formattedList = (result.toolCalls || []).map((tc: Record<string, unknown>) => ({
6565
name: typeof tc.name === 'string' ? tc.name : String(tc.name ?? ''),
66-
arguments: (tc.params && typeof tc.params === 'object' ? tc.params : {}) as Record<
67-
string,
68-
unknown
69-
>,
70-
result: tc.result as any,
66+
...(typeof tc.status === 'string' ? { status: tc.status } : {}),
67+
arguments: (tc.arguments || tc.params || tc.input || {}) as Record<string, unknown>,
68+
result: (tc.result ?? tc.output) as any,
7169
error: typeof tc.error === 'string' ? tc.error : undefined,
7270
duration: typeof tc.durationMs === 'number' ? tc.durationMs : 0,
7371
}))

apps/sim/lib/copilot/chat/payload.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,33 @@ vi.mock('@/tools/utils', () => ({
5353
stripVersionSuffix: vi.fn((toolId: string) => toolId),
5454
}))
5555

56+
vi.mock('@/lib/copilot/integration-tools', () => ({
57+
getExposedIntegrationTools: vi.fn(() => [
58+
{
59+
toolId: 'gmail_send',
60+
config: { id: 'gmail_send', name: 'Gmail Send', description: 'Send emails using Gmail' },
61+
service: 'gmail',
62+
operation: 'send',
63+
},
64+
{
65+
toolId: 'brandfetch_search',
66+
config: {
67+
id: 'brandfetch_search',
68+
name: 'Brandfetch Search',
69+
description: 'Search for brands by company name',
70+
},
71+
service: 'brandfetch',
72+
operation: 'search',
73+
},
74+
{
75+
toolId: 'run_workflow',
76+
config: { id: 'run_workflow', name: 'Run Workflow', description: 'Run a workflow from the client' },
77+
service: 'run',
78+
operation: 'workflow',
79+
},
80+
]),
81+
}))
82+
5683
vi.mock('@/tools/params', () => ({
5784
createUserToolSchema: mockCreateUserToolSchema,
5885
}))

apps/sim/lib/copilot/chat/payload.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { toError } from '@sim/utils/errors'
33
import { LRUCache } from 'lru-cache'
44
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
55
import { isPaid } from '@/lib/billing/plan-helpers'
6+
import { getExposedIntegrationTools } from '@/lib/copilot/integration-tools'
67
import { getToolEntry } from '@/lib/copilot/tool-executor/router'
78
import { getCopilotToolDescription } from '@/lib/copilot/tools/descriptions'
89
import { isHosted } from '@/lib/core/config/feature-flags'
910
import { registerCache } from '@/lib/monitoring/cache-registry'
1011
import { buildMothershipToolsForRequest } from '@/lib/mothership/settings/runtime'
1112
import { trackChatUpload } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
12-
import { tools } from '@/tools/registry'
13-
import { getLatestVersionTools, stripVersionSuffix } from '@/tools/utils'
13+
import { stripVersionSuffix } from '@/tools/utils'
1414

1515
const logger = createLogger('CopilotChatPayload')
1616
const TOOL_SCHEMA_CACHE_TTL_MS = 30_000
@@ -91,7 +91,6 @@ export async function buildIntegrationToolSchemas(
9191
const integrationTools: ToolSchema[] = []
9292
try {
9393
const { createUserToolSchema } = await import('@/tools/params')
94-
const latestTools = getLatestVersionTools(tools)
9594
let shouldAppendEmailTagline = false
9695

9796
try {
@@ -135,24 +134,23 @@ export async function buildIntegrationToolSchemas(
135134
}
136135
}
137136

138-
for (const [toolId, toolConfig] of Object.entries(latestTools)) {
137+
for (const { toolId, config: toolConfig } of getExposedIntegrationTools()) {
139138
try {
140-
const strippedName = stripVersionSuffix(toolId)
141139
if (allowedIntegrations && toolIdToBlockType) {
142-
const owningBlock = toolIdToBlockType.get(strippedName)
140+
const owningBlock = toolIdToBlockType.get(stripVersionSuffix(toolId))
143141
if (owningBlock && !allowedIntegrations.has(owningBlock)) {
144142
continue
145143
}
146144
}
147145
const userSchema = createUserToolSchema(toolConfig, {
148146
surface: options.schemaSurface ?? 'copilot',
149147
})
150-
const catalogEntry = getToolEntry(strippedName)
148+
const catalogEntry = getToolEntry(toolId)
151149
integrationTools.push({
152-
name: strippedName,
150+
name: toolId,
153151
description: getCopilotToolDescription(toolConfig, {
154152
isHosted,
155-
fallbackName: strippedName,
153+
fallbackName: toolId,
156154
appendEmailTagline: shouldAppendEmailTagline,
157155
}),
158156
input_schema: { ...userSchema },

apps/sim/lib/copilot/generated/tool-catalog-v1.ts

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export interface ToolCatalogEntry {
5555
| 'list_folders'
5656
| 'list_user_workspaces'
5757
| 'list_workspace_mcp_servers'
58+
| 'load_integration_tool'
5859
| 'manage_credential'
5960
| 'manage_custom_tool'
6061
| 'manage_job'
@@ -92,7 +93,6 @@ export interface ToolCatalogEntry {
9293
| 'set_global_workflow_variables'
9394
| 'superagent'
9495
| 'table'
95-
| 'tool_search_tool_regex'
9696
| 'touch_plan'
9797
| 'update_job_history'
9898
| 'update_workspace_mcp_server'
@@ -151,6 +151,7 @@ export interface ToolCatalogEntry {
151151
| 'list_folders'
152152
| 'list_user_workspaces'
153153
| 'list_workspace_mcp_servers'
154+
| 'load_integration_tool'
154155
| 'manage_credential'
155156
| 'manage_custom_tool'
156157
| 'manage_job'
@@ -188,7 +189,6 @@ export interface ToolCatalogEntry {
188189
| 'set_global_workflow_variables'
189190
| 'superagent'
190191
| 'table'
191-
| 'tool_search_tool_regex'
192192
| 'touch_plan'
193193
| 'update_job_history'
194194
| 'update_workspace_mcp_server'
@@ -1944,6 +1944,25 @@ export const ListWorkspaceMcpServers: ToolCatalogEntry = {
19441944
},
19451945
}
19461946

1947+
export const LoadIntegrationTool: ToolCatalogEntry = {
1948+
id: 'load_integration_tool',
1949+
name: 'load_integration_tool',
1950+
route: 'sim',
1951+
mode: 'async',
1952+
parameters: {
1953+
properties: {
1954+
tool_ids: {
1955+
description:
1956+
'Exact integration tool ids to load before calling them, e.g. ["gmail_send_v2"]. Copy the "id" field verbatim from components/integrations/{service}/{operation}.json (including any version suffix).',
1957+
items: { type: 'string' },
1958+
type: 'array',
1959+
},
1960+
},
1961+
required: ['tool_ids'],
1962+
type: 'object',
1963+
},
1964+
}
1965+
19471966
export const ManageCredential: ToolCatalogEntry = {
19481967
id: 'manage_credential',
19491968
name: 'manage_credential',
@@ -3029,31 +3048,6 @@ export const Table: ToolCatalogEntry = {
30293048
internal: true,
30303049
}
30313050

3032-
export const ToolSearchToolRegex: ToolCatalogEntry = {
3033-
id: 'tool_search_tool_regex',
3034-
name: 'tool_search_tool_regex',
3035-
route: 'sim',
3036-
mode: 'async',
3037-
parameters: {
3038-
properties: {
3039-
case_insensitive: {
3040-
description: 'Whether the regex should be case-insensitive (default true).',
3041-
type: 'boolean',
3042-
},
3043-
max_results: {
3044-
description: 'Maximum number of tools to return (optional).',
3045-
type: 'integer',
3046-
},
3047-
pattern: {
3048-
description: 'Regular expression to match tool names or descriptions.',
3049-
type: 'string',
3050-
},
3051-
},
3052-
required: ['pattern'],
3053-
type: 'object',
3054-
},
3055-
}
3056-
30573051
export const TouchPlan: ToolCatalogEntry = {
30583052
id: 'touch_plan',
30593053
name: 'touch_plan',
@@ -3896,6 +3890,7 @@ export const TOOL_CATALOG: Record<string, ToolCatalogEntry> = {
38963890
[ListFolders.id]: ListFolders,
38973891
[ListUserWorkspaces.id]: ListUserWorkspaces,
38983892
[ListWorkspaceMcpServers.id]: ListWorkspaceMcpServers,
3893+
[LoadIntegrationTool.id]: LoadIntegrationTool,
38993894
[ManageCredential.id]: ManageCredential,
39003895
[ManageCustomTool.id]: ManageCustomTool,
39013896
[ManageJob.id]: ManageJob,
@@ -3933,7 +3928,6 @@ export const TOOL_CATALOG: Record<string, ToolCatalogEntry> = {
39333928
[SetGlobalWorkflowVariables.id]: SetGlobalWorkflowVariables,
39343929
[Superagent.id]: Superagent,
39353930
[Table.id]: Table,
3936-
[ToolSearchToolRegex.id]: ToolSearchToolRegex,
39373931
[TouchPlan.id]: TouchPlan,
39383932
[UpdateJobHistory.id]: UpdateJobHistory,
39393933
[UpdateWorkspaceMcpServer.id]: UpdateWorkspaceMcpServer,

apps/sim/lib/copilot/generated/tool-schemas-v1.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,23 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
17331733
},
17341734
resultSchema: undefined,
17351735
},
1736+
['load_integration_tool']: {
1737+
parameters: {
1738+
properties: {
1739+
tool_ids: {
1740+
description:
1741+
'Exact integration tool ids to load before calling them, e.g. ["gmail_send_v2"]. Copy the "id" field verbatim from components/integrations/{service}/{operation}.json (including any version suffix).',
1742+
items: {
1743+
type: 'string',
1744+
},
1745+
type: 'array',
1746+
},
1747+
},
1748+
required: ['tool_ids'],
1749+
type: 'object',
1750+
},
1751+
resultSchema: undefined,
1752+
},
17361753
['manage_credential']: {
17371754
parameters: {
17381755
type: 'object',
@@ -2786,27 +2803,6 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
27862803
},
27872804
resultSchema: undefined,
27882805
},
2789-
['tool_search_tool_regex']: {
2790-
parameters: {
2791-
properties: {
2792-
case_insensitive: {
2793-
description: 'Whether the regex should be case-insensitive (default true).',
2794-
type: 'boolean',
2795-
},
2796-
max_results: {
2797-
description: 'Maximum number of tools to return (optional).',
2798-
type: 'integer',
2799-
},
2800-
pattern: {
2801-
description: 'Regular expression to match tool names or descriptions.',
2802-
type: 'string',
2803-
},
2804-
},
2805-
required: ['pattern'],
2806-
type: 'object',
2807-
},
2808-
resultSchema: undefined,
2809-
},
28102806
['touch_plan']: {
28112807
parameters: {
28122808
type: 'object',

0 commit comments

Comments
 (0)