Skip to content

Commit d5c1dbb

Browse files
committed
fix(mship): tool names
1 parent 13b83a3 commit d5c1dbb

5 files changed

Lines changed: 95 additions & 10 deletions

File tree

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,40 @@ describe('completed tool titles', () => {
485485
expect(firstToolTitle(modelToContentBlocks(model))).toBe('Compared workflows')
486486
})
487487

488+
it('humanizes an internal read target through the full wire lifecycle', () => {
489+
const model = createTurnModel()
490+
reduceEvent(
491+
model,
492+
toolEnvelope(
493+
1,
494+
{
495+
phase: 'call',
496+
toolCallId: 'read-oauth-integrations',
497+
toolName: 'read',
498+
arguments: { path: 'environment/oauth-integrations.json' },
499+
},
500+
'auth'
501+
)
502+
)
503+
reduceEvent(
504+
model,
505+
toolEnvelope(
506+
2,
507+
{
508+
phase: 'result',
509+
toolCallId: 'read-oauth-integrations',
510+
toolName: 'read',
511+
success: true,
512+
status: 'success',
513+
output: {},
514+
},
515+
'auth'
516+
)
517+
)
518+
519+
expect(firstToolTitle(modelToContentBlocks(model))).toBe('Read OAuth integrations')
520+
})
521+
488522
it('renders the completed title through the full wire lifecycle for every visible tool', () => {
489523
const hiddenToolNames = getHiddenToolNames()
490524
const failures: string[] = []

apps/sim/lib/copilot/tools/client/store-utils.test.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,35 @@ describe('resolveToolDisplay', () => {
149149
resolveToolDisplay(ReadTool.id, ClientToolCallState.success, {
150150
path: 'components/blocks/unknown_block.json',
151151
})?.text
152-
).toBe('Read unknown_block')
152+
).toBe('Read Unknown block')
153+
})
154+
155+
it('humanizes internal VFS resource identifiers', () => {
156+
expect(
157+
resolveToolDisplay(ReadTool.id, ClientToolCallState.executing, {
158+
path: 'environment/oauth-integrations.json',
159+
})?.text
160+
).toBe('Reading OAuth integrations')
161+
162+
expect(
163+
resolveToolDisplay(ReadTool.id, ClientToolCallState.success, {
164+
path: 'environment/oauth-integrations.json',
165+
})?.text
166+
).toBe('Read OAuth integrations')
167+
168+
expect(
169+
resolveToolDisplay(ReadTool.id, ClientToolCallState.success, {
170+
path: 'environment/api-key-integrations.json',
171+
})?.text
172+
).toBe('Read API key integrations')
153173
})
154174

155175
it('falls back to a humanized tool label for generic tools', () => {
156176
expect(resolveToolDisplay('deploy_api', ClientToolCallState.success)?.text).toBe(
157-
'Executed Deploy Api'
177+
'Executed Deploy API'
178+
)
179+
expect(resolveToolDisplay('oauth-integrations', ClientToolCallState.success)?.text).toBe(
180+
'Executed OAuth Integrations'
158181
)
159182
})
160183

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resources/types'
66
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
77
import { getReadTargetBlock } from '@/lib/copilot/tools/client/read-block'
88
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-call-state'
9+
import { humanizeDisplayIdentifier, humanizeToolName } from '@/lib/copilot/tools/tool-display'
910
import { decodeVfsSegmentSafe } from '@/lib/copilot/vfs/path-utils'
1011

1112
/** Respond tools are internal handoff tools shown with a friendly generic label. */
@@ -98,7 +99,7 @@ function describeReadTarget(path: string | undefined): string | undefined {
9899

99100
const resourceType = VFS_DIR_TO_RESOURCE[segments[0]]
100101
if (!resourceType) {
101-
return stripExtension(segments[segments.length - 1])
102+
return humanizeDisplayIdentifier(stripExtension(segments[segments.length - 1]), 'sentence')
102103
}
103104

104105
if (resourceType === 'file') {
@@ -159,9 +160,9 @@ function humanizedFallback(
159160
toolName: string,
160161
state: ClientToolCallState
161162
): ClientToolDisplay | undefined {
162-
const titleCaseName = toolName.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())
163+
const titleCaseName = humanizeToolName(toolName)
163164
if (state === ClientToolCallState.error) {
164-
const lowerCaseName = toolName.replace(/_/g, ' ').toLowerCase()
165+
const lowerCaseName = humanizeDisplayIdentifier(toolName, 'sentence')
165166
return { text: `Attempted to ${lowerCaseName}`, icon: Loader }
166167
}
167168
const stateVerb =

apps/sim/lib/copilot/tools/tool-display.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ describe('humanizeToolName', () => {
5858
expect(humanizeToolName('manage_folder')).toBe('Manage Folder')
5959
})
6060

61+
it('title-cases kebab-case names', () => {
62+
expect(humanizeToolName('read-oauth-integrations')).toBe('Read OAuth Integrations')
63+
})
64+
6165
it('keeps canonical acronym casing', () => {
6266
expect(humanizeToolName('create_workspace_mcp_server')).toBe('Create Workspace MCP Server')
6367
expect(humanizeToolName('deploy_api')).toBe('Deploy API')
@@ -456,11 +460,15 @@ describe('getToolDisplayTitle for operation-driven tools', () => {
456460
describe('getToolDisplayTitle for request-scoped MCP tools', () => {
457461
it('hides the internal server id and humanizes the tool name', () => {
458462
expect(getToolDisplayTitle('mcp-363de040-web_search_exa')).toBe('Web Search Exa')
463+
expect(getToolDisplayTitle('mcp-363de040-read-oauth-integrations')).toBe(
464+
'Read OAuth Integrations'
465+
)
459466
})
460467
})
461468

462469
describe('getToolDisplayTitle for context management', () => {
463470
it('describes compaction in user-facing language', () => {
464471
expect(getToolDisplayTitle('context_compaction')).toBe('Summarizing context')
472+
expect(getToolStatusDisplayTitle('Summarizing context', 'success')).toBe('Summarized context')
465473
})
466474
})

apps/sim/lib/copilot/tools/tool-display.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -503,17 +503,35 @@ const ACRONYM_CASING: Record<string, string> = {
503503
}
504504

505505
/**
506-
* Final fallback: humanize a raw tool name (e.g. `manage_folder` -> "Manage
507-
* Folder"), matching the legacy client humanizer so labels never render blank.
506+
* Humanize an internal identifier without leaking snake_case or kebab-case into
507+
* the UI. Sentence case is useful for resource names appended to a verb, while
508+
* title case is used for standalone tool-name fallbacks.
508509
*/
509-
export function humanizeToolName(name: string): string {
510-
const words = stripVersionSuffix(name).split('_').filter(Boolean)
510+
export function humanizeDisplayIdentifier(
511+
name: string,
512+
casing: 'sentence' | 'title' = 'title'
513+
): string {
514+
const words = stripVersionSuffix(name).split(/[-_]+/).filter(Boolean)
511515
if (words.length === 0) return name
512516
return words
513-
.map((word) => ACRONYM_CASING[word] ?? word.charAt(0).toUpperCase() + word.slice(1))
517+
.map((word, index) => {
518+
const normalized = word.toLowerCase()
519+
const acronym = ACRONYM_CASING[normalized]
520+
if (acronym) return acronym
521+
if (casing === 'sentence' && index > 0) return normalized
522+
return normalized.charAt(0).toUpperCase() + normalized.slice(1)
523+
})
514524
.join(' ')
515525
}
516526

527+
/**
528+
* Final fallback: humanize a raw tool name (e.g. `manage_folder` -> "Manage
529+
* Folder"), matching the legacy client humanizer so labels never render blank.
530+
*/
531+
export function humanizeToolName(name: string): string {
532+
return humanizeDisplayIdentifier(name)
533+
}
534+
517535
/**
518536
* Resolve a tool-call display title from its name and arguments. Argument-aware
519537
* cases come first, then the static map, then a humanized fallback. This never
@@ -846,6 +864,7 @@ const COMPLETED_VERB_REWRITES: Record<string, string> = {
846864
Scraping: 'Scraped',
847865
Searching: 'Searched',
848866
Setting: 'Set',
867+
Summarizing: 'Summarized',
849868
Syncing: 'Synced',
850869
Toggling: 'Toggled',
851870
Trimming: 'Trimmed',

0 commit comments

Comments
 (0)