Skip to content

Commit 77a4f2f

Browse files
committed
Update tools
1 parent 5954abd commit 77a4f2f

File tree

4 files changed

+169
-24
lines changed

4 files changed

+169
-24
lines changed

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

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

33
import type { ContentBlock, OptionItem, SubagentName, ToolCallData } from '../../types'
44
import { SUBAGENT_LABELS, TOOL_UI_METADATA } from '../../types'
5+
import { resolveToolDisplay } from '@/lib/copilot/store-utils'
6+
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-display-registry'
57
import type { AgentGroupItem } from './components'
68
import { AgentGroup, ChatContent, CircleStop, Options, PendingTagIndicator } from './components'
79

@@ -53,15 +55,40 @@ function resolveAgentLabel(key: string): string {
5355
return SUBAGENT_LABELS[key as SubagentName] ?? formatToolName(key)
5456
}
5557

58+
function mapToolStatusToClientState(status: ContentBlock['toolCall'] extends { status: infer T } ? T : string) {
59+
switch (status) {
60+
case 'success':
61+
return ClientToolCallState.success
62+
case 'error':
63+
return ClientToolCallState.error
64+
case 'cancelled':
65+
return ClientToolCallState.cancelled
66+
default:
67+
return ClientToolCallState.executing
68+
}
69+
}
70+
71+
function getOverrideDisplayTitle(tc: NonNullable<ContentBlock['toolCall']>): string | undefined {
72+
if (tc.name === 'read' || tc.name.endsWith('_respond')) {
73+
return resolveToolDisplay(tc.name, mapToolStatusToClientState(tc.status), tc.id, tc.params)?.text
74+
}
75+
return undefined
76+
}
77+
5678
function toToolData(tc: NonNullable<ContentBlock['toolCall']>): ToolCallData {
79+
const overrideDisplayTitle = getOverrideDisplayTitle(tc)
80+
const displayTitle =
81+
overrideDisplayTitle ||
82+
tc.displayTitle ||
83+
TOOL_UI_METADATA[tc.name as keyof typeof TOOL_UI_METADATA]?.title ||
84+
formatToolName(tc.name)
85+
5786
return {
5887
id: tc.id,
5988
toolName: tc.name,
60-
displayTitle:
61-
tc.displayTitle ||
62-
TOOL_UI_METADATA[tc.name as keyof typeof TOOL_UI_METADATA]?.title ||
63-
formatToolName(tc.name),
89+
displayTitle,
6490
status: tc.status,
91+
params: tc.params,
6592
result: tc.result,
6693
streamingArgs: tc.streamingArgs,
6794
}
@@ -81,25 +108,12 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
81108
const block = blocks[i]
82109

83110
if (block.type === 'subagent_text') {
84-
if (!block.content || !group) continue
85-
const lastItem = group.items[group.items.length - 1]
86-
if (lastItem?.type === 'text') {
87-
lastItem.content += block.content
88-
} else {
89-
group.items.push({ type: 'text', content: block.content })
90-
}
91111
continue
92112
}
93113

94114
if (block.type === 'text') {
95115
if (!block.content?.trim()) continue
96-
if (block.subagent && group && group.agentName === block.subagent) {
97-
const lastItem = group.items[group.items.length - 1]
98-
if (lastItem?.type === 'text') {
99-
lastItem.content += block.content
100-
} else {
101-
group.items.push({ type: 'text', content: block.content })
102-
}
116+
if (block.subagent) {
103117
continue
104118
}
105119
if (group) {
@@ -148,6 +162,7 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
148162
if (block.type === 'tool_call') {
149163
if (!block.toolCall) continue
150164
const tc = block.toolCall
165+
if (tc.name === 'tool_search_tool_regex' || tc.name === 'grep' || tc.name === 'glob') continue
151166
const isDispatch = SUBAGENT_KEYS.has(tc.name) && !tc.calledBy
152167

153168
if (isDispatch) {

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ function mapStoredBlock(block: TaskStoredContentBlock): ContentBlock {
9797
status: resolvedStatus,
9898
displayTitle:
9999
resolvedStatus === 'cancelled' ? 'Stopped by user' : block.toolCall.display?.text,
100+
params: block.toolCall.params,
100101
calledBy: block.toolCall.calledBy,
101102
result: block.toolCall.result,
102103
}
@@ -114,6 +115,7 @@ function mapStoredToolCall(tc: TaskStoredToolCall): ContentBlock {
114115
name: tc.name,
115116
status: resolvedStatus,
116117
displayTitle: resolvedStatus === 'cancelled' ? 'Stopped by user' : undefined,
118+
params: tc.params,
117119
result:
118120
tc.result != null
119121
? {
@@ -736,11 +738,20 @@ export function useChat(
736738
const isPartial = data?.partial === true
737739
if (!id) break
738740

739-
if (name.endsWith('_respond')) break
741+
if (
742+
name === 'tool_search_tool_regex' ||
743+
name === 'grep' ||
744+
name === 'glob'
745+
) {
746+
break
747+
}
740748
const ui = parsed.ui || data?.ui
741749
if (ui?.hidden) break
742750
const displayTitle = ui?.title || ui?.phaseLabel
743751
const phaseLabel = ui?.phaseLabel
752+
const args = (data?.arguments ?? data?.input) as
753+
| Record<string, unknown>
754+
| undefined
744755
if (!toolMap.has(id)) {
745756
toolMap.set(id, blocks.length)
746757
blocks.push({
@@ -751,13 +762,11 @@ export function useChat(
751762
status: 'executing',
752763
displayTitle,
753764
phaseLabel,
765+
params: args,
754766
calledBy: activeSubagent,
755767
},
756768
})
757769
if (name === 'read' || isResourceToolName(name)) {
758-
const args = (data?.arguments ?? data?.input) as
759-
| Record<string, unknown>
760-
| undefined
761770
if (args) toolArgsMap.set(id, args)
762771
}
763772
} else {
@@ -767,6 +776,7 @@ export function useChat(
767776
tc.name = name
768777
if (displayTitle) tc.displayTitle = displayTitle
769778
if (phaseLabel) tc.phaseLabel = phaseLabel
779+
if (args) tc.params = args
770780
}
771781
}
772782
flush()
@@ -1140,6 +1150,7 @@ export function useChat(
11401150
id: block.toolCall.id,
11411151
name: block.toolCall.name,
11421152
state: isCancelled ? 'cancelled' : block.toolCall.status,
1153+
params: block.toolCall.params,
11431154
result: block.toolCall.result,
11441155
display: {
11451156
text: isCancelled ? 'Stopped by user' : block.toolCall.displayTitle,

apps/sim/app/workspace/[workspaceId]/home/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export interface ToolCallData {
145145
toolName: string
146146
displayTitle: string
147147
status: ToolCallStatus
148+
params?: Record<string, unknown>
148149
result?: ToolCallResult
149150
streamingArgs?: string
150151
}
@@ -155,6 +156,7 @@ export interface ToolCallInfo {
155156
status: ToolCallStatus
156157
displayTitle?: string
157158
phaseLabel?: string
159+
params?: Record<string, unknown>
158160
calledBy?: string
159161
result?: { success: boolean; output?: unknown; error?: string }
160162
streamingArgs?: string

apps/sim/lib/copilot/store-utils.ts

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ import {
2828
type ClientToolDisplay,
2929
TOOL_DISPLAY_REGISTRY,
3030
} from '@/lib/copilot/tools/client/tool-display-registry'
31+
import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resource-types'
3132

3233
const logger = createLogger('CopilotStoreUtils')
3334

34-
/** Respond tools are internal to copilot subagents and should never be shown in the UI */
35+
/** Respond tools are internal handoff tools shown with a friendly generic label. */
3536
const HIDDEN_TOOL_SUFFIX = '_respond'
37+
const HIDDEN_TOOL_NAMES = new Set(['tool_search_tool_regex', 'grep', 'glob'])
3638

3739
/** UI metadata sent by the copilot on SSE tool_call events. */
3840
export interface ServerToolUI {
@@ -81,7 +83,11 @@ export function resolveToolDisplay(
8183
serverUI?: ServerToolUI
8284
): ClientToolDisplay | undefined {
8385
if (!toolName) return undefined
84-
if (toolName.endsWith(HIDDEN_TOOL_SUFFIX)) return undefined
86+
if (HIDDEN_TOOL_NAMES.has(toolName)) return undefined
87+
88+
const specialDisplay = specialToolDisplay(toolName, state, params)
89+
if (specialDisplay) return specialDisplay
90+
8591
const entry = TOOL_DISPLAY_REGISTRY[toolName]
8692
if (!entry) {
8793
// Use copilot-provided UI as a better fallback than humanized name
@@ -115,6 +121,117 @@ export function resolveToolDisplay(
115121
return humanizedFallback(toolName, state)
116122
}
117123

124+
function specialToolDisplay(
125+
toolName: string,
126+
state: ClientToolCallState,
127+
params?: Record<string, unknown>
128+
): ClientToolDisplay | undefined {
129+
if (toolName.endsWith(HIDDEN_TOOL_SUFFIX)) {
130+
return {
131+
text: formatRespondLabel(state),
132+
icon: Loader2,
133+
}
134+
}
135+
136+
const searchQuery =
137+
readStringParam(params, 'pattern') || readStringParam(params, 'query') || readStringParam(params, 'glob')
138+
139+
if ((toolName === 'grep' || toolName === 'glob') && searchQuery) {
140+
return {
141+
text: formatSearchingLabel(searchQuery, state),
142+
icon: Search,
143+
}
144+
}
145+
146+
if (toolName === 'read') {
147+
const target = describeReadTarget(readStringParam(params, 'path'))
148+
return {
149+
text: formatReadingLabel(target, state),
150+
icon: FileText,
151+
}
152+
}
153+
154+
return undefined
155+
}
156+
157+
function formatRespondLabel(state: ClientToolCallState): string {
158+
switch (state) {
159+
case ClientToolCallState.success:
160+
return 'Returned results'
161+
case ClientToolCallState.error:
162+
return 'Failed returning results'
163+
case ClientToolCallState.rejected:
164+
case ClientToolCallState.aborted:
165+
return 'Skipped returning results'
166+
default:
167+
return 'Returning results'
168+
}
169+
}
170+
171+
function readStringParam(
172+
params: Record<string, unknown> | undefined,
173+
key: string
174+
): string | undefined {
175+
const value = params?.[key]
176+
return typeof value === 'string' && value.trim() ? value.trim() : undefined
177+
}
178+
179+
function formatSearchingLabel(query: string, state: ClientToolCallState): string {
180+
switch (state) {
181+
case ClientToolCallState.success:
182+
return `Searched for ${query}`
183+
case ClientToolCallState.error:
184+
return `Failed searching for ${query}`
185+
case ClientToolCallState.rejected:
186+
case ClientToolCallState.aborted:
187+
return `Skipped searching for ${query}`
188+
default:
189+
return `Searching for ${query}`
190+
}
191+
}
192+
193+
function formatReadingLabel(target: string | undefined, state: ClientToolCallState): string {
194+
const suffix = target ? ` ${target}` : ''
195+
switch (state) {
196+
case ClientToolCallState.success:
197+
return `Read${suffix}`
198+
case ClientToolCallState.error:
199+
return `Failed reading${suffix}`
200+
case ClientToolCallState.rejected:
201+
case ClientToolCallState.aborted:
202+
return `Skipped reading${suffix}`
203+
default:
204+
return `Reading${suffix}`
205+
}
206+
}
207+
208+
function describeReadTarget(path: string | undefined): string | undefined {
209+
if (!path) return undefined
210+
211+
const segments = path
212+
.split('/')
213+
.map((segment) => segment.trim())
214+
.filter(Boolean)
215+
216+
if (segments.length === 0) return undefined
217+
218+
const resourceType = VFS_DIR_TO_RESOURCE[segments[0]]
219+
if (!resourceType) {
220+
return stripExtension(segments[segments.length - 1])
221+
}
222+
223+
if (resourceType === 'file') {
224+
return segments.slice(1).join('/') || segments[segments.length - 1]
225+
}
226+
227+
const resourceName = segments[1] || segments[segments.length - 1]
228+
return stripExtension(resourceName)
229+
}
230+
231+
function stripExtension(value: string): string {
232+
return value.replace(/\.[^/.]+$/, '')
233+
}
234+
118235
/** Generates display from copilot-provided UI metadata. */
119236
function serverUIFallback(serverUI: ServerToolUI, state: ClientToolCallState): ClientToolDisplay {
120237
const icon = resolveIcon(serverUI.icon)

0 commit comments

Comments
 (0)