mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
Tool call names
This commit is contained in:
@@ -92,7 +92,7 @@ function mapToolStatusToClientState(
|
||||
}
|
||||
|
||||
function getOverrideDisplayTitle(tc: NonNullable<ContentBlock['toolCall']>): string | undefined {
|
||||
if (tc.name === ReadTool.id || tc.name.endsWith('_respond')) {
|
||||
if (tc.name === ReadTool.id || tc.name === 'respond' || tc.name.endsWith('_respond')) {
|
||||
return resolveToolDisplay(tc.name, mapToolStatusToClientState(tc.status), tc.id, tc.params)
|
||||
?.text
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
MothershipStreamV1ToolPhase,
|
||||
} from '@/lib/copilot/generated/mothership-stream-v1'
|
||||
import {
|
||||
CrawlWebsite,
|
||||
CreateFolder,
|
||||
DeleteFolder,
|
||||
DeleteWorkflow,
|
||||
@@ -27,13 +28,33 @@ import {
|
||||
DeployChat,
|
||||
DeployMcp,
|
||||
File as FileTool,
|
||||
GetPageContents,
|
||||
GetWorkflowLogs,
|
||||
Glob,
|
||||
Grep,
|
||||
ManageCredential,
|
||||
ManageCredentialOperation,
|
||||
ManageCustomTool,
|
||||
ManageCustomToolOperation,
|
||||
ManageJob,
|
||||
ManageJobOperation,
|
||||
ManageMcpTool,
|
||||
ManageMcpToolOperation,
|
||||
ManageSkill,
|
||||
ManageSkillOperation,
|
||||
MoveFolder,
|
||||
MoveWorkflow,
|
||||
Read as ReadTool,
|
||||
Redeploy,
|
||||
RenameWorkflow,
|
||||
RunFromBlock,
|
||||
RunWorkflow,
|
||||
RunWorkflowUntilBlock,
|
||||
ScrapePage,
|
||||
SearchOnline,
|
||||
ToolSearchToolRegex,
|
||||
WorkspaceFile,
|
||||
WorkspaceFileOperation,
|
||||
} from '@/lib/copilot/generated/tool-catalog-v1'
|
||||
import type { StreamBatchEvent } from '@/lib/copilot/request/session/types'
|
||||
import {
|
||||
@@ -72,6 +93,7 @@ import type { ChatContext } from '@/stores/panel'
|
||||
import { useTerminalConsoleStore } from '@/stores/terminal'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import type {
|
||||
ChatMessage,
|
||||
ContentBlock,
|
||||
@@ -132,6 +154,314 @@ const logger = createLogger('useChat')
|
||||
|
||||
type StreamPayload = Record<string, unknown>
|
||||
|
||||
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
|
||||
const workspaceId = useWorkflowRegistry.getState().hydration.workspaceId
|
||||
if (!workspaceId) return undefined
|
||||
return getWorkflowById(workspaceId, id)?.name
|
||||
}
|
||||
|
||||
function resolveBlockNameForDisplay(blockId: unknown): string | undefined {
|
||||
const id = stringParam(blockId)
|
||||
if (!id) return undefined
|
||||
return useWorkflowStore.getState().blocks[id]?.name
|
||||
}
|
||||
|
||||
function resolveWorkspaceFileDisplayTitle(
|
||||
operation: unknown,
|
||||
title: unknown,
|
||||
targetFileName?: unknown
|
||||
): string | undefined {
|
||||
const chunkTitle = stringParam(title)
|
||||
const fileName = stringParam(targetFileName)
|
||||
let verb = 'Writing'
|
||||
|
||||
switch (operation) {
|
||||
case WorkspaceFileOperation.append:
|
||||
verb = 'Adding'
|
||||
break
|
||||
case WorkspaceFileOperation.patch:
|
||||
verb = 'Editing'
|
||||
break
|
||||
case WorkspaceFileOperation.update:
|
||||
verb = 'Writing'
|
||||
break
|
||||
}
|
||||
|
||||
if (chunkTitle) return `${verb} ${chunkTitle}`
|
||||
if (fileName) return `${verb} ${fileName}`
|
||||
return undefined
|
||||
}
|
||||
|
||||
function resolveOperationDisplayTitle(
|
||||
operation: unknown,
|
||||
labels: Partial<Record<string, string>>,
|
||||
fallback: string
|
||||
): string {
|
||||
const label = typeof operation === 'string' ? labels[operation] : undefined
|
||||
return label ?? fallback
|
||||
}
|
||||
|
||||
function resolveToolDisplayTitle(name: string, args?: Record<string, unknown>): string | undefined {
|
||||
if (!args) return undefined
|
||||
|
||||
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 === ManageJob.id) {
|
||||
return resolveOperationDisplayTitle(
|
||||
args.operation,
|
||||
{
|
||||
[ManageJobOperation.create]: 'Creating job',
|
||||
[ManageJobOperation.get]: 'Getting job',
|
||||
[ManageJobOperation.update]: 'Updating job',
|
||||
[ManageJobOperation.delete]: 'Deleting job',
|
||||
[ManageJobOperation.list]: 'Listing jobs',
|
||||
},
|
||||
'Job action'
|
||||
)
|
||||
}
|
||||
|
||||
if (name === ManageCredential.id) {
|
||||
return resolveOperationDisplayTitle(
|
||||
args.operation,
|
||||
{
|
||||
[ManageCredentialOperation.rename]: 'Renaming credential',
|
||||
[ManageCredentialOperation.delete]: 'Deleting credential',
|
||||
},
|
||||
'Credential action'
|
||||
)
|
||||
}
|
||||
|
||||
if (name === RunWorkflow.id) {
|
||||
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)
|
||||
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)
|
||||
if (workflowName && blockName) return `Running ${workflowName} until ${blockName}`
|
||||
if (workflowName) return `Running ${workflowName}`
|
||||
if (blockName) return `Running until ${blockName}`
|
||||
return 'Running workflow'
|
||||
}
|
||||
|
||||
if (name === GetWorkflowLogs.id) {
|
||||
const workflowName = resolveWorkflowNameForDisplay(args.workflowId)
|
||||
return workflowName ? `Getting logs for ${workflowName}` : 'Getting logs'
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function decodeStreamingString(value: string): string {
|
||||
return value
|
||||
.replace(/\\u([0-9a-fA-F]{4})/g, (_: string, hex: string) =>
|
||||
String.fromCharCode(Number.parseInt(hex, 16))
|
||||
)
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\\/g, '\\')
|
||||
}
|
||||
|
||||
function matchStreamingStringArg(streamingArgs: string, key: string): string | undefined {
|
||||
const match = streamingArgs.match(new RegExp(`"${key}"\\s*:\\s*"([^"]*)"`, 'm'))
|
||||
return match?.[1] ? decodeStreamingString(match[1]) : undefined
|
||||
}
|
||||
|
||||
function resolveStreamingToolDisplayTitle(name: string, streamingArgs: string): string | undefined {
|
||||
if (name === WorkspaceFile.id) {
|
||||
return resolveWorkspaceFileDisplayTitle(
|
||||
matchStreamingStringArg(streamingArgs, 'operation'),
|
||||
matchStreamingStringArg(streamingArgs, 'title'),
|
||||
matchStreamingStringArg(streamingArgs, 'fileName')
|
||||
)
|
||||
}
|
||||
|
||||
if (name === SearchOnline.id) {
|
||||
const toolTitle = matchStreamingStringArg(streamingArgs, 'toolTitle')
|
||||
return toolTitle ? `Searching online for ${toolTitle}` : undefined
|
||||
}
|
||||
|
||||
if (name === Grep.id) {
|
||||
const toolTitle = matchStreamingStringArg(streamingArgs, 'toolTitle')
|
||||
return toolTitle ? `Searching for ${toolTitle}` : undefined
|
||||
}
|
||||
|
||||
if (name === Glob.id) {
|
||||
const toolTitle = matchStreamingStringArg(streamingArgs, 'toolTitle')
|
||||
return toolTitle ? `Finding ${toolTitle}` : undefined
|
||||
}
|
||||
|
||||
if (name === ScrapePage.id) {
|
||||
const url = matchStreamingStringArg(streamingArgs, 'url')
|
||||
return url ? `Scraping ${url}` : undefined
|
||||
}
|
||||
|
||||
if (name === CrawlWebsite.id) {
|
||||
const url = matchStreamingStringArg(streamingArgs, 'url')
|
||||
return url ? `Crawling ${url}` : undefined
|
||||
}
|
||||
|
||||
if (name === ManageCustomTool.id) {
|
||||
return resolveOperationDisplayTitle(
|
||||
matchStreamingStringArg(streamingArgs, '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(
|
||||
matchStreamingStringArg(streamingArgs, '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(
|
||||
matchStreamingStringArg(streamingArgs, 'operation'),
|
||||
{
|
||||
[ManageSkillOperation.add]: 'Creating skill',
|
||||
[ManageSkillOperation.edit]: 'Updating skill',
|
||||
[ManageSkillOperation.delete]: 'Deleting skill',
|
||||
[ManageSkillOperation.list]: 'Listing skills',
|
||||
},
|
||||
'Skill action'
|
||||
)
|
||||
}
|
||||
|
||||
if (name === ManageJob.id) {
|
||||
return resolveOperationDisplayTitle(
|
||||
matchStreamingStringArg(streamingArgs, 'operation'),
|
||||
{
|
||||
[ManageJobOperation.create]: 'Creating job',
|
||||
[ManageJobOperation.get]: 'Getting job',
|
||||
[ManageJobOperation.update]: 'Updating job',
|
||||
[ManageJobOperation.delete]: 'Deleting job',
|
||||
[ManageJobOperation.list]: 'Listing jobs',
|
||||
},
|
||||
'Job action'
|
||||
)
|
||||
}
|
||||
|
||||
if (name === ManageCredential.id) {
|
||||
return resolveOperationDisplayTitle(
|
||||
matchStreamingStringArg(streamingArgs, 'operation'),
|
||||
{
|
||||
[ManageCredentialOperation.rename]: 'Renaming credential',
|
||||
[ManageCredentialOperation.delete]: 'Deleting credential',
|
||||
},
|
||||
'Credential action'
|
||||
)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
type StreamToolUI = {
|
||||
hidden?: boolean
|
||||
title?: string
|
||||
@@ -1068,35 +1398,8 @@ export function useChat(
|
||||
if (idx !== undefined && blocks[idx].toolCall) {
|
||||
const tc = blocks[idx].toolCall!
|
||||
tc.streamingArgs = (tc.streamingArgs ?? '') + delta
|
||||
|
||||
if (tc.name === WorkspaceFile.id) {
|
||||
const opMatch = tc.streamingArgs.match(/"operation"\s*:\s*"(\w+)"/)
|
||||
const op = opMatch?.[1] ?? ''
|
||||
const verb =
|
||||
op === 'create'
|
||||
? 'Creating'
|
||||
: op === 'append'
|
||||
? 'Adding'
|
||||
: op === 'patch'
|
||||
? 'Editing'
|
||||
: op === 'update'
|
||||
? 'Writing'
|
||||
: op === 'rename'
|
||||
? 'Renaming'
|
||||
: op === 'delete'
|
||||
? 'Deleting'
|
||||
: 'Writing'
|
||||
const titleMatch = tc.streamingArgs.match(/"title"\s*:\s*"([^"]*)"/)
|
||||
if (titleMatch?.[1]) {
|
||||
const unescaped = titleMatch[1]
|
||||
.replace(/\\u([0-9a-fA-F]{4})/g, (_: string, hex: string) =>
|
||||
String.fromCharCode(Number.parseInt(hex, 16))
|
||||
)
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\\/g, '\\')
|
||||
tc.displayTitle = `${verb} ${unescaped}`
|
||||
}
|
||||
}
|
||||
const displayTitle = resolveStreamingToolDisplayTitle(tc.name, tc.streamingArgs)
|
||||
if (displayTitle) tc.displayTitle = displayTitle
|
||||
|
||||
flush()
|
||||
}
|
||||
@@ -1252,31 +1555,7 @@ export function useChat(
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
|
||||
if (name === WorkspaceFile.id) {
|
||||
const operation = typeof args?.operation === 'string' ? args.operation : ''
|
||||
const verb =
|
||||
operation === 'create'
|
||||
? 'Creating'
|
||||
: operation === 'append'
|
||||
? 'Adding'
|
||||
: operation === 'patch'
|
||||
? 'Editing'
|
||||
: operation === 'update'
|
||||
? 'Writing'
|
||||
: operation === 'rename'
|
||||
? 'Renaming'
|
||||
: operation === 'delete'
|
||||
? 'Deleting'
|
||||
: 'Writing'
|
||||
const chunkTitle = args?.title as string | undefined
|
||||
const target = args ? asPayloadRecord(args.target) : undefined
|
||||
const targetFileName = target?.fileName as string | undefined
|
||||
if (chunkTitle) {
|
||||
displayTitle = `${verb} ${chunkTitle}`
|
||||
} else if (targetFileName) {
|
||||
displayTitle = `${verb} ${targetFileName}`
|
||||
}
|
||||
}
|
||||
displayTitle = resolveToolDisplayTitle(name, args) ?? displayTitle
|
||||
|
||||
if (name === 'edit_content') {
|
||||
const parentToolCallId =
|
||||
|
||||
@@ -180,19 +180,19 @@ export interface ChatMessage {
|
||||
}
|
||||
|
||||
export const SUBAGENT_LABELS: Record<string, string> = {
|
||||
workflow: 'Workflow agent',
|
||||
deploy: 'Deploy agent',
|
||||
auth: 'Integration agent',
|
||||
research: 'Research agent',
|
||||
knowledge: 'Knowledge agent',
|
||||
table: 'Table agent',
|
||||
custom_tool: 'Custom Tool agent',
|
||||
workflow: 'Workflow Agent',
|
||||
deploy: 'Deploy Agent',
|
||||
auth: 'Auth Agent',
|
||||
research: 'Research Agent',
|
||||
knowledge: 'Knowledge Agent',
|
||||
table: 'Table Agent',
|
||||
custom_tool: 'Custom Tool Agent',
|
||||
superagent: 'Superagent',
|
||||
debug: 'Debug agent',
|
||||
run: 'Run agent',
|
||||
agent: 'Agent manager',
|
||||
job: 'Job agent',
|
||||
file: 'File',
|
||||
debug: 'Debug Agent',
|
||||
run: 'Run Agent',
|
||||
agent: 'Tools Agent',
|
||||
job: 'Job Agent',
|
||||
file: 'File Agent',
|
||||
} as const
|
||||
|
||||
export interface ToolUIMetadata {
|
||||
@@ -208,12 +208,12 @@ export interface ToolUIMetadata {
|
||||
*/
|
||||
export const TOOL_UI_METADATA: Record<string, ToolUIMetadata> = {
|
||||
[Glob.id]: {
|
||||
title: 'Searching files',
|
||||
title: 'Finding files',
|
||||
phaseLabel: 'Workspace',
|
||||
phase: 'workspace',
|
||||
},
|
||||
[Grep.id]: {
|
||||
title: 'Searching code',
|
||||
title: 'Searching',
|
||||
phaseLabel: 'Workspace',
|
||||
phase: 'workspace',
|
||||
},
|
||||
@@ -239,12 +239,12 @@ export const TOOL_UI_METADATA: Record<string, ToolUIMetadata> = {
|
||||
phase: 'search',
|
||||
},
|
||||
[ManageMcpTool.id]: {
|
||||
title: 'Managing MCP tool',
|
||||
title: 'MCP server action',
|
||||
phaseLabel: 'Management',
|
||||
phase: 'management',
|
||||
},
|
||||
[ManageSkill.id]: {
|
||||
title: 'Managing skill',
|
||||
title: 'Skill action',
|
||||
phaseLabel: 'Management',
|
||||
phase: 'management',
|
||||
},
|
||||
@@ -288,16 +288,16 @@ export const TOOL_UI_METADATA: Record<string, ToolUIMetadata> = {
|
||||
phaseLabel: 'Resource',
|
||||
phase: 'resource',
|
||||
},
|
||||
[Workflow.id]: { title: 'Managing workflow', phaseLabel: 'Workflow', phase: 'subagent' },
|
||||
[Run.id]: { title: 'Running', phaseLabel: 'Run', phase: 'subagent' },
|
||||
[Deploy.id]: { title: 'Deploying', phaseLabel: 'Deploy', phase: 'subagent' },
|
||||
[Workflow.id]: { title: 'Workflow Agent', phaseLabel: 'Workflow', phase: 'subagent' },
|
||||
[Run.id]: { title: 'Run Agent', phaseLabel: 'Run', phase: 'subagent' },
|
||||
[Deploy.id]: { title: 'Deploy Agent', phaseLabel: 'Deploy', phase: 'subagent' },
|
||||
[Auth.id]: {
|
||||
title: 'Connecting credentials',
|
||||
title: 'Auth Agent',
|
||||
phaseLabel: 'Auth',
|
||||
phase: 'subagent',
|
||||
},
|
||||
[Knowledge.id]: {
|
||||
title: 'Managing knowledge',
|
||||
title: 'Knowledge Agent',
|
||||
phaseLabel: 'Knowledge',
|
||||
phase: 'subagent',
|
||||
},
|
||||
@@ -306,16 +306,16 @@ export const TOOL_UI_METADATA: Record<string, ToolUIMetadata> = {
|
||||
phaseLabel: 'Resource',
|
||||
phase: 'resource',
|
||||
},
|
||||
[Table.id]: { title: 'Managing tables', phaseLabel: 'Table', phase: 'subagent' },
|
||||
[Job.id]: { title: 'Managing jobs', phaseLabel: 'Job', phase: 'subagent' },
|
||||
[Agent.id]: { title: 'Agent action', phaseLabel: 'Agent', phase: 'subagent' },
|
||||
[Table.id]: { title: 'Table Agent', phaseLabel: 'Table', phase: 'subagent' },
|
||||
[Job.id]: { title: 'Job Agent', phaseLabel: 'Job', phase: 'subagent' },
|
||||
[Agent.id]: { title: 'Tools Agent', phaseLabel: 'Agent', phase: 'subagent' },
|
||||
custom_tool: {
|
||||
title: 'Creating tool',
|
||||
phaseLabel: 'Tool',
|
||||
phase: 'subagent',
|
||||
},
|
||||
[Research.id]: { title: 'Researching', phaseLabel: 'Research', phase: 'subagent' },
|
||||
[Debug.id]: { title: 'Debugging', phaseLabel: 'Debug', phase: 'subagent' },
|
||||
[Research.id]: { title: 'Research Agent', phaseLabel: 'Research', phase: 'subagent' },
|
||||
[Debug.id]: { title: 'Debug Agent', phaseLabel: 'Debug', phase: 'subagent' },
|
||||
[OpenResource.id]: {
|
||||
title: 'Opening resource',
|
||||
phaseLabel: 'Resource',
|
||||
|
||||
@@ -701,6 +701,38 @@ export const DownloadToWorkspaceFile: ToolCatalogEntry = {
|
||||
requiredPermission: 'write',
|
||||
}
|
||||
|
||||
export const EditContent: ToolCatalogEntry = {
|
||||
id: 'edit_content',
|
||||
name: 'edit_content',
|
||||
executor: 'sim',
|
||||
mode: 'async',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The text content to write. For append: text to append. For update: full replacement text. For patch with search_replace: the replacement text. For patch with anchored: the insert/replacement text.',
|
||||
},
|
||||
},
|
||||
required: ['content'],
|
||||
},
|
||||
resultSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Optional operation metadata such as file id, file name, size, and content type.',
|
||||
},
|
||||
message: { type: 'string', description: 'Human-readable summary of the outcome.' },
|
||||
success: { type: 'boolean', description: 'Whether the content was applied successfully.' },
|
||||
},
|
||||
required: ['success', 'message'],
|
||||
},
|
||||
requiredPermission: 'write',
|
||||
}
|
||||
|
||||
export const EditWorkflow: ToolCatalogEntry = {
|
||||
id: 'edit_workflow',
|
||||
name: 'edit_workflow',
|
||||
@@ -1138,10 +1170,10 @@ export const Glob: ToolCatalogEntry = {
|
||||
description:
|
||||
'Glob pattern to match file paths. Supports * (any segment) and ** (any depth).',
|
||||
},
|
||||
title: {
|
||||
toolTitle: {
|
||||
type: 'string',
|
||||
description:
|
||||
"Short human-readable label shown in the UI while this search runs (e.g. 'Finding workflow configs', 'Listing knowledge bases').",
|
||||
'Optional target-only UI phrase for the search row. The UI verb is supplied for you, so pass text like "workflow configs" or "knowledge bases", not a full sentence like "Finding workflow configs".',
|
||||
},
|
||||
},
|
||||
required: ['pattern'],
|
||||
@@ -1183,10 +1215,10 @@ export const Grep: ToolCatalogEntry = {
|
||||
"Optional path prefix to scope the search (e.g. 'workflows/', 'environment/', 'internal/', 'components/blocks/').",
|
||||
},
|
||||
pattern: { type: 'string', description: 'Regex pattern to search for in file contents.' },
|
||||
title: {
|
||||
toolTitle: {
|
||||
type: 'string',
|
||||
description:
|
||||
"Short human-readable label shown in the UI while this search runs (e.g. 'Searching Slack integrations', 'Finding deployed workflows').",
|
||||
'Optional target-only UI phrase for the search row. The UI verb is supplied for you, so pass text like "Slack integrations" or "deployed workflows", not a full sentence like "Searching for Slack integrations".',
|
||||
},
|
||||
},
|
||||
required: ['pattern'],
|
||||
@@ -2170,6 +2202,11 @@ export const SearchOnline: ToolCatalogEntry = {
|
||||
include_text: { type: 'boolean', description: 'Include page text content (default true)' },
|
||||
num_results: { type: 'number', description: 'Number of results (default 10, max 25)' },
|
||||
query: { type: 'string', description: 'Natural language search query' },
|
||||
toolTitle: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Optional target-only UI phrase for the search row. The UI verb is supplied for you, so pass text like "pricing changes" or "Slack webhook docs", not a full sentence like "Searching online for pricing changes".',
|
||||
},
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
@@ -2593,11 +2630,6 @@ export const WorkspaceFile: ToolCatalogEntry = {
|
||||
type: 'object',
|
||||
description: 'Explicit file target. Use kind=file_id + fileId for existing files.',
|
||||
properties: {
|
||||
kind: {
|
||||
type: 'string',
|
||||
description: 'How the file target is identified.',
|
||||
enum: ['new_file', 'file_id'],
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
description: 'Canonical existing workspace file ID. Required when target.kind=file_id.',
|
||||
@@ -2607,6 +2639,11 @@ export const WorkspaceFile: ToolCatalogEntry = {
|
||||
description:
|
||||
'Plain workspace filename including extension, e.g. "main.py" or "report.docx". Required when target.kind=new_file.',
|
||||
},
|
||||
kind: {
|
||||
type: 'string',
|
||||
description: 'How the file target is identified.',
|
||||
enum: ['new_file', 'file_id'],
|
||||
},
|
||||
},
|
||||
required: ['kind'],
|
||||
},
|
||||
@@ -2635,35 +2672,6 @@ export const WorkspaceFile: ToolCatalogEntry = {
|
||||
description:
|
||||
'Patch metadata. Use strategy=search_replace for exact text replacement, or strategy=anchored for line-based inserts/replacements/deletions. The actual replacement/insert content is provided via the paired edit_content tool call.',
|
||||
properties: {
|
||||
strategy: {
|
||||
type: 'string',
|
||||
description: 'Patch strategy.',
|
||||
enum: ['search_replace', 'anchored'],
|
||||
},
|
||||
search: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Exact text to find when strategy=search_replace. Must match exactly once unless replaceAll=true.',
|
||||
},
|
||||
replaceAll: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'When true and strategy=search_replace, replace every match instead of requiring a unique single match.',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'Anchored edit mode when strategy=anchored.',
|
||||
enum: ['replace_between', 'insert_after', 'delete_between'],
|
||||
},
|
||||
occurrence: {
|
||||
type: 'number',
|
||||
description: '1-based occurrence for repeated anchor lines. Optional; defaults to 1.',
|
||||
},
|
||||
before_anchor: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Boundary line kept before inserted replacement content. Required for mode=replace_between.',
|
||||
},
|
||||
after_anchor: {
|
||||
type: 'string',
|
||||
description:
|
||||
@@ -2674,16 +2682,49 @@ export const WorkspaceFile: ToolCatalogEntry = {
|
||||
description:
|
||||
'Anchor line after which new content is inserted. Required for mode=insert_after.',
|
||||
},
|
||||
start_anchor: {
|
||||
before_anchor: {
|
||||
type: 'string',
|
||||
description: 'First line to delete. Required for mode=delete_between.',
|
||||
description:
|
||||
'Boundary line kept before inserted replacement content. Required for mode=replace_between.',
|
||||
},
|
||||
end_anchor: {
|
||||
type: 'string',
|
||||
description: 'First line to keep after deletion. Required for mode=delete_between.',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'Anchored edit mode when strategy=anchored.',
|
||||
enum: ['replace_between', 'insert_after', 'delete_between'],
|
||||
},
|
||||
occurrence: {
|
||||
type: 'number',
|
||||
description: '1-based occurrence for repeated anchor lines. Optional; defaults to 1.',
|
||||
},
|
||||
replaceAll: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'When true and strategy=search_replace, replace every match instead of requiring a unique single match.',
|
||||
},
|
||||
search: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Exact text to find when strategy=search_replace. Must match exactly once unless replaceAll=true.',
|
||||
},
|
||||
start_anchor: {
|
||||
type: 'string',
|
||||
description: 'First line to delete. Required for mode=delete_between.',
|
||||
},
|
||||
strategy: {
|
||||
type: 'string',
|
||||
description: 'Patch strategy.',
|
||||
enum: ['search_replace', 'anchored'],
|
||||
},
|
||||
},
|
||||
},
|
||||
newName: {
|
||||
type: 'string',
|
||||
description: 'New file name for rename. Must be a plain workspace filename like "main.py".',
|
||||
},
|
||||
},
|
||||
required: ['operation', 'target', 'title'],
|
||||
},
|
||||
@@ -2703,37 +2744,227 @@ export const WorkspaceFile: ToolCatalogEntry = {
|
||||
requiredPermission: 'write',
|
||||
}
|
||||
|
||||
export const EditContent: ToolCatalogEntry = {
|
||||
id: 'edit_content',
|
||||
name: 'edit_content',
|
||||
executor: 'sim',
|
||||
mode: 'async',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The text content to write. For append: text to append. For update: full replacement text. For patch with search_replace: the replacement text. For patch with anchored: the insert/replacement text.',
|
||||
},
|
||||
},
|
||||
required: ['content'],
|
||||
},
|
||||
resultSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Optional operation metadata such as file id, file name, size, and content type.',
|
||||
},
|
||||
message: { type: 'string', description: 'Human-readable summary of the outcome.' },
|
||||
success: { type: 'boolean', description: 'Whether the content was applied successfully.' },
|
||||
},
|
||||
required: ['success', 'message'],
|
||||
},
|
||||
requiredPermission: 'write',
|
||||
}
|
||||
export const KnowledgeBaseOperation = {
|
||||
create: 'create',
|
||||
get: 'get',
|
||||
query: 'query',
|
||||
addFile: 'add_file',
|
||||
update: 'update',
|
||||
delete: 'delete',
|
||||
deleteDocument: 'delete_document',
|
||||
updateDocument: 'update_document',
|
||||
listTags: 'list_tags',
|
||||
createTag: 'create_tag',
|
||||
updateTag: 'update_tag',
|
||||
deleteTag: 'delete_tag',
|
||||
getTagUsage: 'get_tag_usage',
|
||||
addConnector: 'add_connector',
|
||||
updateConnector: 'update_connector',
|
||||
deleteConnector: 'delete_connector',
|
||||
syncConnector: 'sync_connector',
|
||||
} as const
|
||||
|
||||
export type KnowledgeBaseOperation =
|
||||
(typeof KnowledgeBaseOperation)[keyof typeof KnowledgeBaseOperation]
|
||||
|
||||
export const KnowledgeBaseOperationValues = [
|
||||
KnowledgeBaseOperation.create,
|
||||
KnowledgeBaseOperation.get,
|
||||
KnowledgeBaseOperation.query,
|
||||
KnowledgeBaseOperation.addFile,
|
||||
KnowledgeBaseOperation.update,
|
||||
KnowledgeBaseOperation.delete,
|
||||
KnowledgeBaseOperation.deleteDocument,
|
||||
KnowledgeBaseOperation.updateDocument,
|
||||
KnowledgeBaseOperation.listTags,
|
||||
KnowledgeBaseOperation.createTag,
|
||||
KnowledgeBaseOperation.updateTag,
|
||||
KnowledgeBaseOperation.deleteTag,
|
||||
KnowledgeBaseOperation.getTagUsage,
|
||||
KnowledgeBaseOperation.addConnector,
|
||||
KnowledgeBaseOperation.updateConnector,
|
||||
KnowledgeBaseOperation.deleteConnector,
|
||||
KnowledgeBaseOperation.syncConnector,
|
||||
] as const
|
||||
|
||||
export const ManageCredentialOperation = {
|
||||
rename: 'rename',
|
||||
delete: 'delete',
|
||||
} as const
|
||||
|
||||
export type ManageCredentialOperation =
|
||||
(typeof ManageCredentialOperation)[keyof typeof ManageCredentialOperation]
|
||||
|
||||
export const ManageCredentialOperationValues = [
|
||||
ManageCredentialOperation.rename,
|
||||
ManageCredentialOperation.delete,
|
||||
] as const
|
||||
|
||||
export const ManageCustomToolOperation = {
|
||||
add: 'add',
|
||||
edit: 'edit',
|
||||
delete: 'delete',
|
||||
list: 'list',
|
||||
} as const
|
||||
|
||||
export type ManageCustomToolOperation =
|
||||
(typeof ManageCustomToolOperation)[keyof typeof ManageCustomToolOperation]
|
||||
|
||||
export const ManageCustomToolOperationValues = [
|
||||
ManageCustomToolOperation.add,
|
||||
ManageCustomToolOperation.edit,
|
||||
ManageCustomToolOperation.delete,
|
||||
ManageCustomToolOperation.list,
|
||||
] as const
|
||||
|
||||
export const ManageJobOperation = {
|
||||
create: 'create',
|
||||
list: 'list',
|
||||
get: 'get',
|
||||
update: 'update',
|
||||
delete: 'delete',
|
||||
} as const
|
||||
|
||||
export type ManageJobOperation = (typeof ManageJobOperation)[keyof typeof ManageJobOperation]
|
||||
|
||||
export const ManageJobOperationValues = [
|
||||
ManageJobOperation.create,
|
||||
ManageJobOperation.list,
|
||||
ManageJobOperation.get,
|
||||
ManageJobOperation.update,
|
||||
ManageJobOperation.delete,
|
||||
] as const
|
||||
|
||||
export const ManageMcpToolOperation = {
|
||||
add: 'add',
|
||||
edit: 'edit',
|
||||
delete: 'delete',
|
||||
list: 'list',
|
||||
} as const
|
||||
|
||||
export type ManageMcpToolOperation =
|
||||
(typeof ManageMcpToolOperation)[keyof typeof ManageMcpToolOperation]
|
||||
|
||||
export const ManageMcpToolOperationValues = [
|
||||
ManageMcpToolOperation.add,
|
||||
ManageMcpToolOperation.edit,
|
||||
ManageMcpToolOperation.delete,
|
||||
ManageMcpToolOperation.list,
|
||||
] as const
|
||||
|
||||
export const ManageSkillOperation = {
|
||||
add: 'add',
|
||||
edit: 'edit',
|
||||
delete: 'delete',
|
||||
list: 'list',
|
||||
} as const
|
||||
|
||||
export type ManageSkillOperation = (typeof ManageSkillOperation)[keyof typeof ManageSkillOperation]
|
||||
|
||||
export const ManageSkillOperationValues = [
|
||||
ManageSkillOperation.add,
|
||||
ManageSkillOperation.edit,
|
||||
ManageSkillOperation.delete,
|
||||
ManageSkillOperation.list,
|
||||
] as const
|
||||
|
||||
export const MaterializeFileOperation = {
|
||||
save: 'save',
|
||||
import: 'import',
|
||||
table: 'table',
|
||||
knowledgeBase: 'knowledge_base',
|
||||
} as const
|
||||
|
||||
export type MaterializeFileOperation =
|
||||
(typeof MaterializeFileOperation)[keyof typeof MaterializeFileOperation]
|
||||
|
||||
export const MaterializeFileOperationValues = [
|
||||
MaterializeFileOperation.save,
|
||||
MaterializeFileOperation.import,
|
||||
MaterializeFileOperation.table,
|
||||
MaterializeFileOperation.knowledgeBase,
|
||||
] as const
|
||||
|
||||
export const UserMemoryOperation = {
|
||||
add: 'add',
|
||||
search: 'search',
|
||||
delete: 'delete',
|
||||
correct: 'correct',
|
||||
list: 'list',
|
||||
} as const
|
||||
|
||||
export type UserMemoryOperation = (typeof UserMemoryOperation)[keyof typeof UserMemoryOperation]
|
||||
|
||||
export const UserMemoryOperationValues = [
|
||||
UserMemoryOperation.add,
|
||||
UserMemoryOperation.search,
|
||||
UserMemoryOperation.delete,
|
||||
UserMemoryOperation.correct,
|
||||
UserMemoryOperation.list,
|
||||
] as const
|
||||
|
||||
export const UserTableOperation = {
|
||||
create: 'create',
|
||||
createFromFile: 'create_from_file',
|
||||
importFile: 'import_file',
|
||||
get: 'get',
|
||||
getSchema: 'get_schema',
|
||||
delete: 'delete',
|
||||
insertRow: 'insert_row',
|
||||
batchInsertRows: 'batch_insert_rows',
|
||||
getRow: 'get_row',
|
||||
queryRows: 'query_rows',
|
||||
updateRow: 'update_row',
|
||||
deleteRow: 'delete_row',
|
||||
updateRowsByFilter: 'update_rows_by_filter',
|
||||
deleteRowsByFilter: 'delete_rows_by_filter',
|
||||
batchUpdateRows: 'batch_update_rows',
|
||||
batchDeleteRows: 'batch_delete_rows',
|
||||
addColumn: 'add_column',
|
||||
renameColumn: 'rename_column',
|
||||
deleteColumn: 'delete_column',
|
||||
updateColumn: 'update_column',
|
||||
} as const
|
||||
|
||||
export type UserTableOperation = (typeof UserTableOperation)[keyof typeof UserTableOperation]
|
||||
|
||||
export const UserTableOperationValues = [
|
||||
UserTableOperation.create,
|
||||
UserTableOperation.createFromFile,
|
||||
UserTableOperation.importFile,
|
||||
UserTableOperation.get,
|
||||
UserTableOperation.getSchema,
|
||||
UserTableOperation.delete,
|
||||
UserTableOperation.insertRow,
|
||||
UserTableOperation.batchInsertRows,
|
||||
UserTableOperation.getRow,
|
||||
UserTableOperation.queryRows,
|
||||
UserTableOperation.updateRow,
|
||||
UserTableOperation.deleteRow,
|
||||
UserTableOperation.updateRowsByFilter,
|
||||
UserTableOperation.deleteRowsByFilter,
|
||||
UserTableOperation.batchUpdateRows,
|
||||
UserTableOperation.batchDeleteRows,
|
||||
UserTableOperation.addColumn,
|
||||
UserTableOperation.renameColumn,
|
||||
UserTableOperation.deleteColumn,
|
||||
UserTableOperation.updateColumn,
|
||||
] as const
|
||||
|
||||
export const WorkspaceFileOperation = {
|
||||
append: 'append',
|
||||
update: 'update',
|
||||
patch: 'patch',
|
||||
} as const
|
||||
|
||||
export type WorkspaceFileOperation =
|
||||
(typeof WorkspaceFileOperation)[keyof typeof WorkspaceFileOperation]
|
||||
|
||||
export const WorkspaceFileOperationValues = [
|
||||
WorkspaceFileOperation.append,
|
||||
WorkspaceFileOperation.update,
|
||||
WorkspaceFileOperation.patch,
|
||||
] as const
|
||||
|
||||
export const TOOL_CATALOG: Record<string, ToolCatalogEntry> = {
|
||||
[Agent.id]: Agent,
|
||||
@@ -2757,6 +2988,7 @@ export const TOOL_CATALOG: Record<string, ToolCatalogEntry> = {
|
||||
[DeployChat.id]: DeployChat,
|
||||
[DeployMcp.id]: DeployMcp,
|
||||
[DownloadToWorkspaceFile.id]: DownloadToWorkspaceFile,
|
||||
[EditContent.id]: EditContent,
|
||||
[EditWorkflow.id]: EditWorkflow,
|
||||
[File.id]: File,
|
||||
[FunctionExecute.id]: FunctionExecute,
|
||||
@@ -2820,5 +3052,4 @@ export const TOOL_CATALOG: Record<string, ToolCatalogEntry> = {
|
||||
[UserTable.id]: UserTable,
|
||||
[Workflow.id]: Workflow,
|
||||
[WorkspaceFile.id]: WorkspaceFile,
|
||||
[EditContent.id]: EditContent,
|
||||
}
|
||||
|
||||
@@ -513,6 +513,38 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
},
|
||||
resultSchema: undefined,
|
||||
},
|
||||
edit_content: {
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The text content to write. For append: text to append. For update: full replacement text. For patch with search_replace: the replacement text. For patch with anchored: the insert/replacement text.',
|
||||
},
|
||||
},
|
||||
required: ['content'],
|
||||
},
|
||||
resultSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Optional operation metadata such as file id, file name, size, and content type.',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Human-readable summary of the outcome.',
|
||||
},
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the content was applied successfully.',
|
||||
},
|
||||
},
|
||||
required: ['success', 'message'],
|
||||
},
|
||||
},
|
||||
edit_workflow: {
|
||||
parameters: {
|
||||
type: 'object',
|
||||
@@ -928,10 +960,10 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
description:
|
||||
'Glob pattern to match file paths. Supports * (any segment) and ** (any depth).',
|
||||
},
|
||||
title: {
|
||||
toolTitle: {
|
||||
type: 'string',
|
||||
description:
|
||||
"Short human-readable label shown in the UI while this search runs (e.g. 'Finding workflow configs', 'Listing knowledge bases').",
|
||||
'Optional target-only UI phrase for the search row. The UI verb is supplied for you, so pass text like "workflow configs" or "knowledge bases", not a full sentence like "Finding workflow configs".',
|
||||
},
|
||||
},
|
||||
required: ['pattern'],
|
||||
@@ -975,10 +1007,10 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
type: 'string',
|
||||
description: 'Regex pattern to search for in file contents.',
|
||||
},
|
||||
title: {
|
||||
toolTitle: {
|
||||
type: 'string',
|
||||
description:
|
||||
"Short human-readable label shown in the UI while this search runs (e.g. 'Searching Slack integrations', 'Finding deployed workflows').",
|
||||
'Optional target-only UI phrase for the search row. The UI verb is supplied for you, so pass text like "Slack integrations" or "deployed workflows", not a full sentence like "Searching for Slack integrations".',
|
||||
},
|
||||
},
|
||||
required: ['pattern'],
|
||||
@@ -1936,6 +1968,11 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
type: 'string',
|
||||
description: 'Natural language search query',
|
||||
},
|
||||
toolTitle: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Optional target-only UI phrase for the search row. The UI verb is supplied for you, so pass text like "pricing changes" or "Slack webhook docs", not a full sentence like "Searching online for pricing changes".',
|
||||
},
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
@@ -2361,11 +2398,6 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
type: 'object',
|
||||
description: 'Explicit file target. Use kind=file_id + fileId for existing files.',
|
||||
properties: {
|
||||
kind: {
|
||||
type: 'string',
|
||||
description: 'How the file target is identified.',
|
||||
enum: ['new_file', 'file_id'],
|
||||
},
|
||||
fileId: {
|
||||
type: 'string',
|
||||
description:
|
||||
@@ -2376,6 +2408,11 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
description:
|
||||
'Plain workspace filename including extension, e.g. "main.py" or "report.docx". Required when target.kind=new_file.',
|
||||
},
|
||||
kind: {
|
||||
type: 'string',
|
||||
description: 'How the file target is identified.',
|
||||
enum: ['new_file', 'file_id'],
|
||||
},
|
||||
},
|
||||
required: ['kind'],
|
||||
},
|
||||
@@ -2404,35 +2441,6 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
description:
|
||||
'Patch metadata. Use strategy=search_replace for exact text replacement, or strategy=anchored for line-based inserts/replacements/deletions. The actual replacement/insert content is provided via the paired edit_content tool call.',
|
||||
properties: {
|
||||
strategy: {
|
||||
type: 'string',
|
||||
description: 'Patch strategy.',
|
||||
enum: ['search_replace', 'anchored'],
|
||||
},
|
||||
search: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Exact text to find when strategy=search_replace. Must match exactly once unless replaceAll=true.',
|
||||
},
|
||||
replaceAll: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'When true and strategy=search_replace, replace every match instead of requiring a unique single match.',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'Anchored edit mode when strategy=anchored.',
|
||||
enum: ['replace_between', 'insert_after', 'delete_between'],
|
||||
},
|
||||
occurrence: {
|
||||
type: 'number',
|
||||
description: '1-based occurrence for repeated anchor lines. Optional; defaults to 1.',
|
||||
},
|
||||
before_anchor: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Boundary line kept before inserted replacement content. Required for mode=replace_between.',
|
||||
},
|
||||
after_anchor: {
|
||||
type: 'string',
|
||||
description:
|
||||
@@ -2443,16 +2451,50 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
description:
|
||||
'Anchor line after which new content is inserted. Required for mode=insert_after.',
|
||||
},
|
||||
start_anchor: {
|
||||
before_anchor: {
|
||||
type: 'string',
|
||||
description: 'First line to delete. Required for mode=delete_between.',
|
||||
description:
|
||||
'Boundary line kept before inserted replacement content. Required for mode=replace_between.',
|
||||
},
|
||||
end_anchor: {
|
||||
type: 'string',
|
||||
description: 'First line to keep after deletion. Required for mode=delete_between.',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'Anchored edit mode when strategy=anchored.',
|
||||
enum: ['replace_between', 'insert_after', 'delete_between'],
|
||||
},
|
||||
occurrence: {
|
||||
type: 'number',
|
||||
description: '1-based occurrence for repeated anchor lines. Optional; defaults to 1.',
|
||||
},
|
||||
replaceAll: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'When true and strategy=search_replace, replace every match instead of requiring a unique single match.',
|
||||
},
|
||||
search: {
|
||||
type: 'string',
|
||||
description:
|
||||
'Exact text to find when strategy=search_replace. Must match exactly once unless replaceAll=true.',
|
||||
},
|
||||
start_anchor: {
|
||||
type: 'string',
|
||||
description: 'First line to delete. Required for mode=delete_between.',
|
||||
},
|
||||
strategy: {
|
||||
type: 'string',
|
||||
description: 'Patch strategy.',
|
||||
enum: ['search_replace', 'anchored'],
|
||||
},
|
||||
},
|
||||
},
|
||||
newName: {
|
||||
type: 'string',
|
||||
description:
|
||||
'New file name for rename. Must be a plain workspace filename like "main.py".',
|
||||
},
|
||||
},
|
||||
required: ['operation', 'target', 'title'],
|
||||
},
|
||||
@@ -2476,36 +2518,4 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
|
||||
required: ['success', 'message'],
|
||||
},
|
||||
},
|
||||
edit_content: {
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The text content to write. For append: text to append. For update: full replacement text. For patch with search_replace: the replacement text. For patch with anchored: the insert/replacement text.',
|
||||
},
|
||||
},
|
||||
required: ['content'],
|
||||
},
|
||||
resultSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Optional operation metadata such as file id, file name, size, and content type.',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Human-readable summary of the outcome.',
|
||||
},
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the content was applied successfully.',
|
||||
},
|
||||
},
|
||||
required: ['success', 'message'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ const logger = createLogger('CopilotStoreUtils')
|
||||
|
||||
/** Respond tools are internal handoff tools shown with a friendly generic label. */
|
||||
const HIDDEN_TOOL_SUFFIX = '_respond'
|
||||
const INTERNAL_RESPOND_TOOL = 'respond'
|
||||
const HIDDEN_TOOL_NAMES = new Set(['tool_search_tool_regex'])
|
||||
|
||||
/** UI metadata sent by the copilot on SSE tool_call events. */
|
||||
@@ -127,7 +128,7 @@ function specialToolDisplay(
|
||||
state: ClientToolCallState,
|
||||
params?: Record<string, unknown>
|
||||
): ClientToolDisplay | undefined {
|
||||
if (toolName.endsWith(HIDDEN_TOOL_SUFFIX)) {
|
||||
if (toolName === INTERNAL_RESPOND_TOOL || toolName.endsWith(HIDDEN_TOOL_SUFFIX)) {
|
||||
return {
|
||||
text: formatRespondLabel(state),
|
||||
icon: Loader2,
|
||||
@@ -146,17 +147,8 @@ function specialToolDisplay(
|
||||
}
|
||||
|
||||
function formatRespondLabel(state: ClientToolCallState): string {
|
||||
switch (state) {
|
||||
case ClientToolCallState.success:
|
||||
return 'Returned results'
|
||||
case ClientToolCallState.error:
|
||||
return 'Failed returning results'
|
||||
case ClientToolCallState.rejected:
|
||||
case ClientToolCallState.aborted:
|
||||
return 'Skipped returning results'
|
||||
default:
|
||||
return 'Returning results'
|
||||
}
|
||||
void state
|
||||
return 'Gathering thoughts'
|
||||
}
|
||||
|
||||
function readStringParam(
|
||||
|
||||
@@ -38,6 +38,11 @@ import {
|
||||
XCircle,
|
||||
Zap,
|
||||
} from 'lucide-react'
|
||||
import {
|
||||
ManageCustomToolOperation,
|
||||
ManageMcpToolOperation,
|
||||
ManageSkillOperation,
|
||||
} from '@/lib/copilot/generated/tool-catalog-v1'
|
||||
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
|
||||
import type { CustomToolDefinition } from '@/hooks/queries/custom-tools'
|
||||
import type { WorkflowDeploymentInfo } from '@/hooks/queries/deployments'
|
||||
@@ -227,6 +232,15 @@ const META_check_deployment_status: ToolMetadata = {
|
||||
interrupt: undefined,
|
||||
}
|
||||
|
||||
const META_complete_job: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: { text: 'Completing job', icon: Loader2 },
|
||||
[ClientToolCallState.executing]: { text: 'Completing job', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Completed job', icon: CheckCircle },
|
||||
[ClientToolCallState.error]: { text: 'Failed to complete job', icon: XCircle },
|
||||
},
|
||||
}
|
||||
|
||||
const META_checkoff_todo: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: { text: 'Marking todo', icon: Loader2 },
|
||||
@@ -270,6 +284,30 @@ const META_crawl_website: ToolMetadata = {
|
||||
},
|
||||
}
|
||||
|
||||
const META_create_file: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: { text: 'Creating file', icon: Loader2 },
|
||||
[ClientToolCallState.executing]: { text: 'Creating file', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Created file', icon: FileText },
|
||||
[ClientToolCallState.error]: { text: 'Failed to create file', icon: XCircle },
|
||||
},
|
||||
getDynamicText: (params, state) => {
|
||||
const fileName = params?.fileName
|
||||
if (typeof fileName !== 'string' || !fileName.trim()) return undefined
|
||||
|
||||
switch (state) {
|
||||
case ClientToolCallState.success:
|
||||
return `Created ${fileName}`
|
||||
case ClientToolCallState.executing:
|
||||
case ClientToolCallState.generating:
|
||||
return `Creating ${fileName}`
|
||||
case ClientToolCallState.error:
|
||||
return `Failed to create ${fileName}`
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
}
|
||||
|
||||
const META_create_workspace_mcp_server: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: {
|
||||
@@ -348,19 +386,19 @@ const META_agent: ToolMetadata = {
|
||||
const META_manage_skill: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: {
|
||||
text: 'Managing skill',
|
||||
text: 'Skill action',
|
||||
icon: Loader2,
|
||||
},
|
||||
[ClientToolCallState.pending]: { text: 'Manage skill?', icon: BookOpen },
|
||||
[ClientToolCallState.executing]: { text: 'Managing skill', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Managed skill', icon: Check },
|
||||
[ClientToolCallState.error]: { text: 'Failed to manage skill', icon: X },
|
||||
[ClientToolCallState.pending]: { text: 'Skill action?', icon: BookOpen },
|
||||
[ClientToolCallState.executing]: { text: 'Skill action', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Updated skill', icon: Check },
|
||||
[ClientToolCallState.error]: { text: 'Failed skill action', icon: X },
|
||||
[ClientToolCallState.aborted]: {
|
||||
text: 'Aborted managing skill',
|
||||
text: 'Aborted skill action',
|
||||
icon: XCircle,
|
||||
},
|
||||
[ClientToolCallState.rejected]: {
|
||||
text: 'Skipped managing skill',
|
||||
text: 'Skipped skill action',
|
||||
icon: XCircle,
|
||||
},
|
||||
},
|
||||
@@ -368,6 +406,66 @@ const META_manage_skill: ToolMetadata = {
|
||||
accept: { text: 'Allow', icon: Check },
|
||||
reject: { text: 'Deny', icon: X },
|
||||
},
|
||||
getDynamicText: (params, state) => {
|
||||
const operation = params?.operation as ManageSkillOperation | undefined
|
||||
if (!operation) return undefined
|
||||
|
||||
const skillName = typeof params?.name === 'string' ? params.name : 'skill'
|
||||
|
||||
switch (state) {
|
||||
case ClientToolCallState.success:
|
||||
switch (operation) {
|
||||
case ManageSkillOperation.add:
|
||||
return `Created ${skillName}`
|
||||
case ManageSkillOperation.edit:
|
||||
return `Updated ${skillName}`
|
||||
case ManageSkillOperation.delete:
|
||||
return `Deleted ${skillName}`
|
||||
case ManageSkillOperation.list:
|
||||
return 'Listed skills'
|
||||
}
|
||||
break
|
||||
case ClientToolCallState.executing:
|
||||
case ClientToolCallState.generating:
|
||||
switch (operation) {
|
||||
case ManageSkillOperation.add:
|
||||
return `Creating ${skillName}`
|
||||
case ManageSkillOperation.edit:
|
||||
return `Updating ${skillName}`
|
||||
case ManageSkillOperation.delete:
|
||||
return `Deleting ${skillName}`
|
||||
case ManageSkillOperation.list:
|
||||
return 'Listing skills'
|
||||
}
|
||||
break
|
||||
case ClientToolCallState.pending:
|
||||
switch (operation) {
|
||||
case ManageSkillOperation.add:
|
||||
return `Create ${skillName}?`
|
||||
case ManageSkillOperation.edit:
|
||||
return `Update ${skillName}?`
|
||||
case ManageSkillOperation.delete:
|
||||
return `Delete ${skillName}?`
|
||||
case ManageSkillOperation.list:
|
||||
return 'List skills?'
|
||||
}
|
||||
break
|
||||
case ClientToolCallState.error:
|
||||
switch (operation) {
|
||||
case ManageSkillOperation.add:
|
||||
return `Failed to create ${skillName}`
|
||||
case ManageSkillOperation.edit:
|
||||
return `Failed to update ${skillName}`
|
||||
case ManageSkillOperation.delete:
|
||||
return `Failed to delete ${skillName}`
|
||||
case ManageSkillOperation.list:
|
||||
return 'Failed to list skills'
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return undefined
|
||||
},
|
||||
}
|
||||
|
||||
const META_workflow: ToolMetadata = {
|
||||
@@ -962,19 +1060,19 @@ const META_list_workspace_mcp_servers: ToolMetadata = {
|
||||
const META_manage_custom_tool: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: {
|
||||
text: 'Managing custom tool',
|
||||
text: 'Custom tool action',
|
||||
icon: Loader2,
|
||||
},
|
||||
[ClientToolCallState.pending]: { text: 'Manage custom tool?', icon: Plus },
|
||||
[ClientToolCallState.executing]: { text: 'Managing custom tool', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Managed custom tool', icon: Check },
|
||||
[ClientToolCallState.error]: { text: 'Failed to manage custom tool', icon: X },
|
||||
[ClientToolCallState.pending]: { text: 'Custom tool action?', icon: Plus },
|
||||
[ClientToolCallState.executing]: { text: 'Custom tool action', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Updated custom tool', icon: Check },
|
||||
[ClientToolCallState.error]: { text: 'Failed custom tool action', icon: X },
|
||||
[ClientToolCallState.aborted]: {
|
||||
text: 'Aborted managing custom tool',
|
||||
text: 'Aborted custom tool action',
|
||||
icon: XCircle,
|
||||
},
|
||||
[ClientToolCallState.rejected]: {
|
||||
text: 'Skipped managing custom tool',
|
||||
text: 'Skipped custom tool action',
|
||||
icon: XCircle,
|
||||
},
|
||||
},
|
||||
@@ -983,7 +1081,7 @@ const META_manage_custom_tool: ToolMetadata = {
|
||||
reject: { text: 'Skip', icon: XCircle },
|
||||
},
|
||||
getDynamicText: (params, state) => {
|
||||
const operation = params?.operation as 'add' | 'edit' | 'delete' | 'list' | undefined
|
||||
const operation = params?.operation as ManageCustomToolOperation | undefined
|
||||
const workspaceId = getScopedWorkspaceId(params)
|
||||
|
||||
if (!operation) return undefined
|
||||
@@ -1004,13 +1102,13 @@ const META_manage_custom_tool: ToolMetadata = {
|
||||
|
||||
const getActionText = (verb: 'present' | 'past' | 'gerund') => {
|
||||
switch (operation) {
|
||||
case 'add':
|
||||
case ManageCustomToolOperation.add:
|
||||
return verb === 'present' ? 'Create' : verb === 'past' ? 'Created' : 'Creating'
|
||||
case 'edit':
|
||||
case ManageCustomToolOperation.edit:
|
||||
return verb === 'present' ? 'Edit' : verb === 'past' ? 'Edited' : 'Editing'
|
||||
case 'delete':
|
||||
case ManageCustomToolOperation.delete:
|
||||
return verb === 'present' ? 'Delete' : verb === 'past' ? 'Deleted' : 'Deleting'
|
||||
case 'list':
|
||||
case ManageCustomToolOperation.list:
|
||||
return verb === 'present' ? 'List' : verb === 'past' ? 'Listed' : 'Listing'
|
||||
default:
|
||||
return verb === 'present' ? 'Manage' : verb === 'past' ? 'Managed' : 'Managing'
|
||||
@@ -1021,15 +1119,15 @@ const META_manage_custom_tool: ToolMetadata = {
|
||||
// For edit/delete: always show tool name
|
||||
// For list: never show individual tool name, use plural
|
||||
const shouldShowToolName = (currentState: ClientToolCallState) => {
|
||||
if (operation === 'list') return false
|
||||
if (operation === 'add') {
|
||||
if (operation === ManageCustomToolOperation.list) return false
|
||||
if (operation === ManageCustomToolOperation.add) {
|
||||
return currentState === ClientToolCallState.success
|
||||
}
|
||||
return true // edit and delete always show tool name
|
||||
}
|
||||
|
||||
const nameText =
|
||||
operation === 'list'
|
||||
operation === ManageCustomToolOperation.list
|
||||
? ' custom tools'
|
||||
: shouldShowToolName(state) && toolName
|
||||
? ` ${toolName}`
|
||||
@@ -1058,19 +1156,19 @@ const META_manage_custom_tool: ToolMetadata = {
|
||||
const META_manage_mcp_tool: ToolMetadata = {
|
||||
displayNames: {
|
||||
[ClientToolCallState.generating]: {
|
||||
text: 'Managing MCP tool',
|
||||
text: 'MCP server action',
|
||||
icon: Loader2,
|
||||
},
|
||||
[ClientToolCallState.pending]: { text: 'Manage MCP tool?', icon: Server },
|
||||
[ClientToolCallState.executing]: { text: 'Managing MCP tool', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Managed MCP tool', icon: Check },
|
||||
[ClientToolCallState.error]: { text: 'Failed to manage MCP tool', icon: X },
|
||||
[ClientToolCallState.pending]: { text: 'MCP server action?', icon: Server },
|
||||
[ClientToolCallState.executing]: { text: 'MCP server action', icon: Loader2 },
|
||||
[ClientToolCallState.success]: { text: 'Updated MCP server', icon: Check },
|
||||
[ClientToolCallState.error]: { text: 'Failed MCP server action', icon: X },
|
||||
[ClientToolCallState.aborted]: {
|
||||
text: 'Aborted managing MCP tool',
|
||||
text: 'Aborted MCP server action',
|
||||
icon: XCircle,
|
||||
},
|
||||
[ClientToolCallState.rejected]: {
|
||||
text: 'Skipped managing MCP tool',
|
||||
text: 'Skipped MCP server action',
|
||||
icon: XCircle,
|
||||
},
|
||||
},
|
||||
@@ -1079,7 +1177,7 @@ const META_manage_mcp_tool: ToolMetadata = {
|
||||
reject: { text: 'Skip', icon: XCircle },
|
||||
},
|
||||
getDynamicText: (params, state) => {
|
||||
const operation = params?.operation as 'add' | 'edit' | 'delete' | undefined
|
||||
const operation = params?.operation as ManageMcpToolOperation | undefined
|
||||
|
||||
if (!operation) return undefined
|
||||
|
||||
@@ -1087,23 +1185,31 @@ const META_manage_mcp_tool: ToolMetadata = {
|
||||
|
||||
const getActionText = (verb: 'present' | 'past' | 'gerund') => {
|
||||
switch (operation) {
|
||||
case 'add':
|
||||
case ManageMcpToolOperation.add:
|
||||
return verb === 'present' ? 'Add' : verb === 'past' ? 'Added' : 'Adding'
|
||||
case 'edit':
|
||||
case ManageMcpToolOperation.edit:
|
||||
return verb === 'present' ? 'Edit' : verb === 'past' ? 'Edited' : 'Editing'
|
||||
case 'delete':
|
||||
case ManageMcpToolOperation.delete:
|
||||
return verb === 'present' ? 'Delete' : verb === 'past' ? 'Deleted' : 'Deleting'
|
||||
case ManageMcpToolOperation.list:
|
||||
return verb === 'present' ? 'List' : verb === 'past' ? 'Listed' : 'Listing'
|
||||
}
|
||||
}
|
||||
|
||||
const shouldShowServerName = (currentState: ClientToolCallState) => {
|
||||
if (operation === 'add') {
|
||||
if (operation === ManageMcpToolOperation.list) return false
|
||||
if (operation === ManageMcpToolOperation.add) {
|
||||
return currentState === ClientToolCallState.success
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const nameText = shouldShowServerName(state) && serverName ? ` ${serverName}` : ' MCP tool'
|
||||
const nameText =
|
||||
operation === ManageMcpToolOperation.list
|
||||
? ' MCP servers'
|
||||
: shouldShowServerName(state) && serverName
|
||||
? ` ${serverName}`
|
||||
: ' MCP server'
|
||||
|
||||
switch (state) {
|
||||
case ClientToolCallState.success:
|
||||
@@ -2229,8 +2335,10 @@ const TOOL_METADATA_BY_ID: Record<string, ToolMetadata> = {
|
||||
auth: META_auth,
|
||||
context_compaction: META_context_compaction,
|
||||
check_deployment_status: META_check_deployment_status,
|
||||
complete_job: META_complete_job,
|
||||
checkoff_todo: META_checkoff_todo,
|
||||
crawl_website: META_crawl_website,
|
||||
create_file: META_create_file,
|
||||
create_workspace_mcp_server: META_create_workspace_mcp_server,
|
||||
workflow: META_workflow,
|
||||
create_folder: META_create_folder,
|
||||
|
||||
@@ -4,10 +4,7 @@ import { fileURLToPath } from 'node:url'
|
||||
|
||||
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
|
||||
const ROOT = resolve(SCRIPT_DIR, '..')
|
||||
const DEFAULT_CATALOG_PATH = resolve(
|
||||
ROOT,
|
||||
'../copilot/copilot/contracts/tool-catalog-v1.json'
|
||||
)
|
||||
const DEFAULT_CATALOG_PATH = resolve(ROOT, '../copilot/copilot/contracts/tool-catalog-v1.json')
|
||||
const OUTPUT_PATH = resolve(ROOT, 'apps/sim/lib/copilot/generated/tool-catalog-v1.ts')
|
||||
const RUNTIME_SCHEMA_OUTPUT_PATH = resolve(
|
||||
ROOT,
|
||||
@@ -15,14 +12,56 @@ const RUNTIME_SCHEMA_OUTPUT_PATH = resolve(
|
||||
)
|
||||
|
||||
function snakeToPascal(s: string): string {
|
||||
return s.split('_').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join('')
|
||||
return s
|
||||
.split('_')
|
||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
||||
.join('')
|
||||
}
|
||||
|
||||
function toCamelIdentifier(value: string): string {
|
||||
const parts = value.split(/[^a-zA-Z0-9]+/).filter(Boolean)
|
||||
if (parts.length === 0) return 'value'
|
||||
|
||||
const camel = parts
|
||||
.map((part, index) => {
|
||||
const lower = part.toLowerCase()
|
||||
if (index === 0) return lower
|
||||
return lower.charAt(0).toUpperCase() + lower.slice(1)
|
||||
})
|
||||
.join('')
|
||||
|
||||
return /^[0-9]/.test(camel) ? `v${camel}` : camel
|
||||
}
|
||||
|
||||
function getTopLevelOperationEnum(tool: Record<string, unknown>): string[] | undefined {
|
||||
const parameters =
|
||||
typeof tool.parameters === 'object' && tool.parameters !== null
|
||||
? (tool.parameters as Record<string, unknown>)
|
||||
: null
|
||||
const properties =
|
||||
parameters && typeof parameters.properties === 'object' && parameters.properties !== null
|
||||
? (parameters.properties as Record<string, unknown>)
|
||||
: null
|
||||
const operation =
|
||||
properties && typeof properties.operation === 'object' && properties.operation !== null
|
||||
? (properties.operation as Record<string, unknown>)
|
||||
: null
|
||||
const values = operation?.enum
|
||||
|
||||
if (!Array.isArray(values) || values.some((value) => typeof value !== 'string')) {
|
||||
return undefined
|
||||
}
|
||||
return values as string[]
|
||||
}
|
||||
|
||||
function inferTSType(values: unknown[]): string {
|
||||
const unique = [...new Set(values.filter((v) => v !== undefined && v !== null))]
|
||||
if (unique.length === 0) return 'string'
|
||||
if (unique.every((v) => typeof v === 'string')) {
|
||||
return unique.map((v) => JSON.stringify(v)).sort().join(' | ')
|
||||
return unique
|
||||
.map((v) => JSON.stringify(v))
|
||||
.sort()
|
||||
.join(' | ')
|
||||
}
|
||||
if (unique.every((v) => typeof v === 'boolean')) return 'boolean'
|
||||
if (unique.every((v) => typeof v === 'number')) return 'number'
|
||||
@@ -47,7 +86,8 @@ function renderRuntimeSchemaModule(catalog: { tools: Record<string, unknown>[] }
|
||||
|
||||
for (const tool of catalog.tools) {
|
||||
const id = JSON.stringify(tool.id)
|
||||
const parameters = 'parameters' in tool ? JSON.stringify(tool.parameters ?? null, null, 2) : 'undefined'
|
||||
const parameters =
|
||||
'parameters' in tool ? JSON.stringify(tool.parameters ?? null, null, 2) : 'undefined'
|
||||
const resultSchema =
|
||||
'resultSchema' in tool ? JSON.stringify(tool.resultSchema ?? null, null, 2) : 'undefined'
|
||||
lines.push(` [${id}]: {`)
|
||||
@@ -96,7 +136,9 @@ function generateInterface(tools: Record<string, unknown>[]): string {
|
||||
async function main() {
|
||||
const checkOnly = process.argv.includes('--check')
|
||||
const inputPathArg = process.argv.find((arg) => arg.startsWith('--input='))
|
||||
const inputPath = inputPathArg ? resolve(ROOT, inputPathArg.slice('--input='.length)) : DEFAULT_CATALOG_PATH
|
||||
const inputPath = inputPathArg
|
||||
? resolve(ROOT, inputPathArg.slice('--input='.length))
|
||||
: DEFAULT_CATALOG_PATH
|
||||
|
||||
const raw = await readFile(inputPath, 'utf8')
|
||||
const catalog = JSON.parse(raw) as { version: string; tools: Record<string, unknown>[] }
|
||||
@@ -122,11 +164,43 @@ async function main() {
|
||||
fields.push(` ${key}: ${JSON.stringify(value)}`)
|
||||
}
|
||||
lines.push(`export const ${constName}: ToolCatalogEntry = {`)
|
||||
lines.push(fields.join(',\n') + ',')
|
||||
lines.push(`${fields.join(',\n')},`)
|
||||
lines.push('};')
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
for (const tool of catalog.tools) {
|
||||
const constName = snakeToPascal(tool.id as string)
|
||||
const operationEnum = getTopLevelOperationEnum(tool)
|
||||
if (!operationEnum || operationEnum.length === 0) continue
|
||||
|
||||
const operationConstName = `${constName}Operation`
|
||||
const seenKeys = new Set<string>()
|
||||
const members = operationEnum.map((value, index) => {
|
||||
let key = toCamelIdentifier(value)
|
||||
if (seenKeys.has(key)) key = `${key}${index + 1}`
|
||||
seenKeys.add(key)
|
||||
return { key, value }
|
||||
})
|
||||
|
||||
lines.push(`export const ${operationConstName} = {`)
|
||||
for (const member of members) {
|
||||
lines.push(` ${member.key}: ${JSON.stringify(member.value)},`)
|
||||
}
|
||||
lines.push('} as const;')
|
||||
lines.push('')
|
||||
lines.push(
|
||||
`export type ${operationConstName} = (typeof ${operationConstName})[keyof typeof ${operationConstName}];`
|
||||
)
|
||||
lines.push('')
|
||||
lines.push(`export const ${operationConstName}Values = [`)
|
||||
for (const member of members) {
|
||||
lines.push(` ${operationConstName}.${member.key},`)
|
||||
}
|
||||
lines.push(`] as const;`)
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
lines.push(`export const TOOL_CATALOG: Record<string, ToolCatalogEntry> = {`)
|
||||
for (let i = 0; i < catalog.tools.length; i++) {
|
||||
lines.push(` [${constNames[i]}.id]: ${constNames[i]},`)
|
||||
@@ -141,9 +215,7 @@ async function main() {
|
||||
const existing = await readFile(OUTPUT_PATH, 'utf8').catch(() => null)
|
||||
const existingRuntime = await readFile(RUNTIME_SCHEMA_OUTPUT_PATH, 'utf8').catch(() => null)
|
||||
if (existing !== rendered || existingRuntime !== runtimeSchemaRendered) {
|
||||
throw new Error(
|
||||
`Generated tool catalog is stale. Run: bun run mship-tools:generate`
|
||||
)
|
||||
throw new Error(`Generated tool catalog is stale. Run: bun run mship-tools:generate`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user