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
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
'use client'

import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { stripVersionSuffix } from '@sim/utils/string'
import { Read as ReadTool, WorkspaceFile } from '@/lib/copilot/generated/tool-catalog-v1'
import { isToolHiddenInUi } from '@/lib/copilot/tools/client/hidden-tools'
import { resolveToolDisplay } from '@/lib/copilot/tools/client/store-utils'
import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-call-state'
import { getToolDisplayTitle, humanizeToolName } from '@/lib/copilot/tools/tool-display'
import { useChatSurface } from '@/app/workspace/[workspaceId]/home/components/chat-surface-context'
import type { ContentBlock, OptionItem, ToolCallData } from '../../types'
import { SUBAGENT_LABELS, TOOL_UI_METADATA } from '../../types'
import { SUBAGENT_LABELS } from '../../types'
import type { AgentGroupItem } from './components'
import {
AgentGroup,
Expand Down Expand Up @@ -84,16 +84,9 @@ function isHiddenToolCall(toolName: string | undefined): boolean {
return isToolHiddenInUi(toolName)
}

function formatToolName(name: string): string {
return stripVersionSuffix(name)
.split('_')
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ')
}

function resolveAgentLabel(key: string): string {
if (key === 'mothership') return 'Sim'
return SUBAGENT_LABELS[key] ?? formatToolName(key)
return SUBAGENT_LABELS[key] ?? humanizeToolName(key)
}

function isDelegatingTool(tc: NonNullable<ContentBlock['toolCall']>): boolean {
Expand Down Expand Up @@ -129,10 +122,7 @@ function getOverrideDisplayTitle(tc: NonNullable<ContentBlock['toolCall']>): str
function toToolData(tc: NonNullable<ContentBlock['toolCall']>): ToolCallData {
const overrideDisplayTitle = getOverrideDisplayTitle(tc)
const displayTitle =
overrideDisplayTitle ||
tc.displayTitle ||
TOOL_UI_METADATA[tc.name as keyof typeof TOOL_UI_METADATA]?.title ||
formatToolName(tc.name)
overrideDisplayTitle || tc.displayTitle || getToolDisplayTitle(tc.name, tc.params)

return {
id: tc.id,
Expand Down
162 changes: 29 additions & 133 deletions apps/sim/app/workspace/[workspaceId]/home/hooks/stream/stream-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@ import { createLogger } from '@sim/logger'
import { isRecordLike } from '@sim/utils/object'
import {
CrawlWebsite,
CreateFolder,
DeleteFolder,
DeleteWorkflow,
DeployApi,
DeployChat,
DeployMcp,
FunctionExecute,
GetPageContents,
Glob,
Grep,
ManageCredential,
ManageCredentialOperation,
ManageCustomTool,
ManageCustomToolOperation,
ManageFolder,
ManageFolderOperation,
ManageMcpTool,
ManageMcpToolOperation,
ManageScheduledTask,
ManageScheduledTaskOperation,
ManageSkill,
ManageSkillOperation,
MoveFolder,
MoveWorkflow,
QueryLogs,
Redeploy,
Expand All @@ -36,6 +34,7 @@ import {
WorkspaceFileOperation,
} from '@/lib/copilot/generated/tool-catalog-v1'
import { VFS_DIR_TO_RESOURCE } from '@/lib/copilot/resources/types'
import { getToolDisplayTitle } from '@/lib/copilot/tools/tool-display'
import type { ContentBlock, MothershipResource } from '@/app/workspace/[workspaceId]/home/types'
import { ToolCallStatus } from '@/app/workspace/[workspaceId]/home/types'
import { getWorkflowById } from '@/hooks/queries/utils/workflow-cache'
Expand All @@ -53,11 +52,7 @@ export const DEPLOY_TOOL_NAMES: Set<string> = new Set([
Redeploy.id,
])

export const FOLDER_TOOL_NAMES: Set<string> = new Set([
CreateFolder.id,
DeleteFolder.id,
MoveFolder.id,
])
export const FOLDER_TOOL_NAMES: Set<string> = new Set([ManageFolder.id])

export const WORKFLOW_MUTATION_TOOL_NAMES: Set<string> = new Set([
MoveWorkflow.id,
Expand Down Expand Up @@ -160,11 +155,6 @@ function stringParam(value: unknown): string | undefined {
return typeof value === 'string' && value.trim() ? value.trim() : undefined
}

function stringArrayParam(value: unknown): string[] {
if (!Array.isArray(value)) return []
return value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
}

function resolveWorkflowNameForDisplay(workflowId: unknown): string | undefined {
const id = stringParam(workflowId)
if (!id) return undefined
Expand Down Expand Up @@ -218,134 +208,27 @@ function functionExecuteTitle(title: string | undefined): string {
return title ?? 'Running code'
}

export function resolveToolDisplayTitle(
name: string,
args?: Record<string, unknown>
): string | undefined {
if (!args) return undefined

if (name === FunctionExecute.id) {
return functionExecuteTitle(stringParam(args.title))
}

if (name === WorkspaceFile.id) {
const target = asPayloadRecord(args.target)
return resolveWorkspaceFileDisplayTitle(args.operation, args.title, target?.fileName)
}

if (name === SearchOnline.id) {
const toolTitle = stringParam(args.toolTitle)
return toolTitle ? `Searching online for ${toolTitle}` : 'Searching online'
}

if (name === Grep.id) {
const toolTitle = stringParam(args.toolTitle)
return toolTitle ? `Searching for ${toolTitle}` : 'Searching'
}

if (name === Glob.id) {
const toolTitle = stringParam(args.toolTitle)
return toolTitle ? `Finding ${toolTitle}` : 'Finding files'
}

if (name === ScrapePage.id) {
const url = stringParam(args.url)
return url ? `Scraping ${url}` : 'Scraping page'
}

if (name === CrawlWebsite.id) {
const url = stringParam(args.url)
return url ? `Crawling ${url}` : 'Crawling website'
}

if (name === GetPageContents.id) {
const urls = stringArrayParam(args.urls)
if (urls.length === 1) return `Getting ${urls[0]}`
if (urls.length > 1) return `Getting ${urls.length} pages`
return 'Getting page contents'
}

if (name === ManageCustomTool.id) {
return resolveOperationDisplayTitle(
args.operation,
{
[ManageCustomToolOperation.add]: 'Creating custom tool',
[ManageCustomToolOperation.edit]: 'Updating custom tool',
[ManageCustomToolOperation.delete]: 'Deleting custom tool',
[ManageCustomToolOperation.list]: 'Listing custom tools',
},
'Custom tool action'
)
}

if (name === ManageMcpTool.id) {
return resolveOperationDisplayTitle(
args.operation,
{
[ManageMcpToolOperation.add]: 'Creating MCP server',
[ManageMcpToolOperation.edit]: 'Updating MCP server',
[ManageMcpToolOperation.delete]: 'Deleting MCP server',
[ManageMcpToolOperation.list]: 'Listing MCP servers',
},
'MCP server action'
)
}

if (name === ManageSkill.id) {
return resolveOperationDisplayTitle(
args.operation,
{
[ManageSkillOperation.add]: 'Creating skill',
[ManageSkillOperation.edit]: 'Updating skill',
[ManageSkillOperation.delete]: 'Deleting skill',
[ManageSkillOperation.list]: 'Listing skills',
},
'Skill action'
)
}

if (name === ManageScheduledTask.id) {
return resolveOperationDisplayTitle(
args.operation,
{
[ManageScheduledTaskOperation.create]: 'Creating scheduled task',
[ManageScheduledTaskOperation.get]: 'Getting scheduled task',
[ManageScheduledTaskOperation.update]: 'Updating scheduled task',
[ManageScheduledTaskOperation.delete]: 'Deleting scheduled task',
[ManageScheduledTaskOperation.list]: 'Listing scheduled tasks',
},
'Scheduled task action'
)
}

if (name === ManageCredential.id) {
return resolveOperationDisplayTitle(
args.operation,
{
[ManageCredentialOperation.rename]: 'Renaming credential',
[ManageCredentialOperation.delete]: 'Deleting credential',
},
'Credential action'
)
}

export function resolveToolDisplayTitle(name: string, args?: Record<string, unknown>): string {
// Cases that enrich the title with live workspace/block names from the client
// stores. Everything else is resolved by the shared name+args resolver, which
// is the single source of truth for tool-call titles.
if (name === RunWorkflow.id) {
const workflowName = resolveWorkflowNameForDisplay(args.workflowId)
const workflowName = resolveWorkflowNameForDisplay(args?.workflowId)
return workflowName ? `Running ${workflowName}` : 'Running workflow'
}

if (name === RunFromBlock.id) {
const workflowName = resolveWorkflowNameForDisplay(args.workflowId)
const blockName = resolveBlockNameForDisplay(args.startBlockId)
const workflowName = resolveWorkflowNameForDisplay(args?.workflowId)
const blockName = resolveBlockNameForDisplay(args?.startBlockId)
if (workflowName && blockName) return `Running ${workflowName} from ${blockName}`
if (workflowName) return `Running ${workflowName}`
if (blockName) return `Running from ${blockName}`
return 'Running workflow'
}

if (name === RunWorkflowUntilBlock.id) {
const workflowName = resolveWorkflowNameForDisplay(args.workflowId)
const blockName = resolveBlockNameForDisplay(args.stopAfterBlockId)
const workflowName = resolveWorkflowNameForDisplay(args?.workflowId)
const blockName = resolveBlockNameForDisplay(args?.stopAfterBlockId)
if (workflowName && blockName) return `Running ${workflowName} until ${blockName}`
if (workflowName) return `Running ${workflowName}`
if (blockName) return `Running until ${blockName}`
Expand All @@ -354,11 +237,11 @@ export function resolveToolDisplayTitle(

if (name === QueryLogs.id) {
const workflowName =
resolveWorkflowNameForDisplay(args.workflowId) ?? stringParam(args.workflowName)
return workflowName ? `Querying logs for ${workflowName}` : undefined
resolveWorkflowNameForDisplay(args?.workflowId) ?? stringParam(args?.workflowName)
if (workflowName) return `Querying logs for ${workflowName}`
}

return undefined
return getToolDisplayTitle(name, args)
}

function decodeStreamingString(value: string): string {
Expand Down Expand Up @@ -480,5 +363,18 @@ export function resolveStreamingToolDisplayTitle(
)
}

if (name === ManageFolder.id) {
return resolveOperationDisplayTitle(
matchStreamingStringArg(streamingArgs, 'operation'),
{
[ManageFolderOperation.create]: 'Creating folder',
[ManageFolderOperation.rename]: 'Renaming folder',
[ManageFolderOperation.move]: 'Moving folder',
[ManageFolderOperation.delete]: 'Deleting folder',
},
'Folder action'
)
}

return undefined
}
Original file line number Diff line number Diff line change
Expand Up @@ -477,9 +477,9 @@ export function reduceEvent(model: TurnModel, envelope: PersistedStreamEventEnve
tsMs
)
if (isRecord(payload.arguments)) node.args = payload.arguments
// Tool-call titles are derived from the tool name (+args) at serialize
// time; the stream only carries behavioral flags now.
const ui = isRecord(payload.ui) ? payload.ui : undefined
const uiTitle = ui ? (asString(ui.title) ?? asString(ui.phaseLabel)) : undefined
if (uiTitle) node.uiTitle = uiTitle
if (ui?.hidden === true) node.hidden = true
} else if (phase === MothershipStreamV1ToolPhase.args_delta) {
const node = upsertToolNode(
Expand Down
79 changes: 0 additions & 79 deletions apps/sim/app/workspace/[workspaceId]/home/types.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,3 @@
import {
Agent,
Auth,
CreateWorkflow,
Deploy,
EditWorkflow,
Ffmpeg,
FunctionExecute,
GenerateAudio,
GenerateImage,
GenerateVideo,
GetPageContents,
Glob,
Grep,
Knowledge,
KnowledgeBase,
ManageMcpTool,
ManageSkill,
Media,
OpenResource,
Read as ReadTool,
Research,
ScheduledTask,
ScrapePage,
SearchLibraryDocs,
SearchOnline,
Superagent,
Table,
UserMemory,
UserTable,
Workflow,
WorkspaceFile,
} from '@/lib/copilot/generated/tool-catalog-v1'
import type { ChatContext } from '@/stores/panel'

const EDIT_CONTENT_TOOL_ID = 'edit_content'
Expand Down Expand Up @@ -199,49 +166,3 @@ export const SUBAGENT_LABELS: Record<string, string> = {
file: 'File Agent',
media: 'Media Agent',
} as const

interface ToolTitleMetadata {
title: string
}

/**
* Fallback titles for tool calls when the stream did not provide one.
*/
export const TOOL_UI_METADATA: Record<string, ToolTitleMetadata> = {
[Glob.id]: { title: 'Finding files' },
[Grep.id]: { title: 'Searching' },
[ReadTool.id]: { title: 'Reading file' },
[SearchOnline.id]: { title: 'Searching online' },
[ScrapePage.id]: { title: 'Scraping page' },
[GetPageContents.id]: { title: 'Getting page contents' },
[SearchLibraryDocs.id]: { title: 'Searching library docs' },
[ManageMcpTool.id]: { title: 'MCP server action' },
[ManageSkill.id]: { title: 'Skill action' },
[UserMemory.id]: { title: 'Accessing memory' },
[FunctionExecute.id]: { title: 'Running code' },
[Superagent.id]: { title: 'Executing action' },
[UserTable.id]: { title: 'Managing table' },
[WorkspaceFile.id]: { title: 'Editing file' },
[EDIT_CONTENT_TOOL_ID]: { title: 'Applying file content' },
[CreateWorkflow.id]: { title: 'Creating workflow' },
[EditWorkflow.id]: { title: 'Editing workflow' },
[Workflow.id]: { title: 'Workflow Agent' },
[RUN_SUBAGENT_ID]: { title: 'Run Agent' },
[Deploy.id]: { title: 'Deploy Agent' },
[Auth.id]: { title: 'Auth Agent' },
[Knowledge.id]: { title: 'Knowledge Agent' },
[KnowledgeBase.id]: { title: 'Managing knowledge base' },
[Table.id]: { title: 'Table Agent' },
[ScheduledTask.id]: { title: 'Scheduled Task Agent' },
job: { title: 'Job Agent' },
[Agent.id]: { title: 'Tools Agent' },
custom_tool: { title: 'Creating tool' },
[Research.id]: { title: 'Research Agent' },
[OpenResource.id]: { title: 'Opening resource' },
[Media.id]: { title: 'Media Agent' },
[GenerateImage.id]: { title: 'Generating image' },
[GenerateVideo.id]: { title: 'Generating video' },
[GenerateAudio.id]: { title: 'Generating audio' },
[Ffmpeg.id]: { title: 'Processing media' },
context_compaction: { title: 'Compacted context' },
}
Loading
Loading