diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx index b803b64006..e28f4f51d4 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx @@ -107,11 +107,19 @@ export function ToolCallItem({ toolName, displayTitle, status, streamingArgs }: const opMatch = streamingArgs.match(/"operation"\s*:\s*"(\w+)"/) const op = opMatch?.[1] ?? '' const verb = - op === 'patch' || op === 'update' || op === 'rename' - ? 'Editing' - : op === 'delete' - ? 'Deleting' - : 'Writing' + op === 'create' + ? 'Creating' + : op === 'append' + ? 'Adding' + : op === 'patch' + ? 'Editing' + : op === 'update' + ? 'Writing' + : op === 'rename' + ? 'Renaming' + : op === 'delete' + ? 'Deleting' + : 'Writing' const unescaped = titleMatch[1] .replace(/\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(Number.parseInt(hex, 16)) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx index 7ba6cc8979..f4b53efd06 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/chat-content/chat-content.tsx @@ -155,7 +155,7 @@ const MARKDOWN_COMPONENTS = { }, inlineCode({ children }: { children?: React.ReactNode }) { return ( - + {children} ) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts index 4c66041de2..c799930ea9 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts @@ -41,6 +41,7 @@ const TOOL_ICONS: Record = { superagent: Blimp, user_table: TableIcon, workspace_file: File, + edit_content: File, create_workflow: Layout, edit_workflow: Pencil, workflow: Hammer, diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx index 6a0796f930..78bc6b2394 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx @@ -103,20 +103,22 @@ export const ResourceContent = memo(function ResourceContent({ const isUpdateStream = streamOperation === 'update' const { data: allFiles = [] } = useWorkspaceFiles(workspaceId) - const activeFileRecord = useMemo(() => { - if (!isPatchStream || resource.type !== 'file') return undefined - return allFiles.find((f) => f.id === resource.id) - }, [isPatchStream, resource, allFiles]) + const previewFileId = + streamingFile?.fileId ?? (resource.type === 'file' ? resource.id : undefined) + const previewFileRecord = useMemo(() => { + if (!previewFileId) return undefined + return allFiles.find((f) => f.id === previewFileId) + }, [previewFileId, allFiles]) const isSourceMime = - activeFileRecord?.type === 'text/x-pptxgenjs' || - activeFileRecord?.type === 'text/x-docxjs' || - activeFileRecord?.type === 'text/x-pdflibjs' + previewFileRecord?.type === 'text/x-pptxgenjs' || + previewFileRecord?.type === 'text/x-docxjs' || + previewFileRecord?.type === 'text/x-pdflibjs' const { data: fetchedFileContent } = useWorkspaceFileContent( workspaceId, - activeFileRecord?.id ?? '', - activeFileRecord?.key ?? '', + previewFileRecord?.id ?? '', + previewFileRecord?.key ?? '', isSourceMime ) @@ -125,15 +127,28 @@ export const ResourceContent = memo(function ResourceContent({ if (!streamOperation) return undefined if (isPatchStream) { - if (!fetchedFileContent) return undefined + if (fetchedFileContent === undefined) return undefined + if (!shouldApplyPatchPreview(streamingFile)) return undefined return extractPatchPreview(streamingFile, fetchedFileContent) } const extracted = streamingFile.content - if (extracted.length === 0) return undefined if (isUpdateStream) return extracted - if (isWriteStream) return extracted + + if (streamOperation === 'append') { + if (streamingFile.targetKind === 'file_id') { + if (fetchedFileContent === undefined) return undefined + return buildAppendPreview(fetchedFileContent, extracted) + } + return extracted.length > 0 ? extracted : undefined + } + + if (streamOperation === 'create') { + return extracted.length > 0 ? extracted : undefined + } + + if (isWriteStream) return extracted.length > 0 ? extracted : undefined return undefined }, [ @@ -165,16 +180,11 @@ export const ResourceContent = memo(function ResourceContent({ } }, [workspaceId, streamFileName]) - // workspace_file preview events now carry whole-file snapshots, not deltas. - // Treat every live preview as replace so the viewer shows the latest snapshot. + // ResourceContent now reconstructs full-file preview text per operation, + // so the viewer can always treat streaming content as a whole-file replace. const streamingFileMode: 'append' | 'replace' = 'replace' - // For existing file resources (not streaming-file), only pass streaming - // content for patch operations where the preview splices new content into - // the displayed file. Update operations re-stream the entire file from - // scratch which causes visual duplication of already-visible content. - const embeddedStreamingContent = - resource.id !== 'streaming-file' && isUpdateStream ? undefined : streamingExtractedContent + const embeddedStreamingContent = streamingExtractedContent if (streamingFile && resource.id === 'streaming-file') { return ( @@ -700,3 +710,27 @@ function extractPatchPreview( return undefined } + +function shouldApplyPatchPreview(streamingFile: { + content: string + edit?: Record +}): boolean { + const edit = streamingFile.edit ?? {} + const strategy = typeof edit.strategy === 'string' ? edit.strategy : undefined + const mode = typeof edit.mode === 'string' ? edit.mode : undefined + + // delete_between is delete-only and can be previewed from intent metadata alone. + if (strategy === 'anchored' && mode === 'delete_between') { + return true + } + + // For all other patch modes, keep the visible file unchanged until + // edit_content actually streams content into the target location. + return streamingFile.content.length > 0 +} + +function buildAppendPreview(existingContent: string, incomingContent: string): string { + if (incomingContent.length === 0) return existingContent + if (existingContent.length === 0) return incomingContent + return `${existingContent}\n${incomingContent}` +} diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index 938aea4bed..45ce8fb70c 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -354,6 +354,7 @@ export function useChat( streamingFileRef.current = streamingFile const filePreviewSessionsRef = useRef>(new Map()) const activeFilePreviewToolCallIdRef = useRef(null) + const editContentParentToolCallIdRef = useRef>(new Map()) const [messageQueue, setMessageQueue] = useState([]) const messageQueueRef = useRef([]) @@ -368,6 +369,15 @@ export function useChat( options?: { preserveExistingState?: boolean } ) => Promise<{ sawStreamError: boolean; sawComplete: boolean }> >(async () => ({ sawStreamError: false, sawComplete: false })) + const attachToExistingStreamRef = useRef< + (opts: { + streamId: string + assistantId: string + expectedGen: number + initialBatch?: StreamBatchResponse | null + afterCursor?: string + }) => Promise<{ error: boolean; aborted: boolean }> + >(async () => ({ error: false, aborted: true })) const retryReconnectRef = useRef< (opts: { streamId: string; assistantId: string; gen: number }) => Promise >(async () => false) @@ -518,6 +528,7 @@ export function useChat( streamingFileRef.current = null filePreviewSessionsRef.current.clear() activeFilePreviewToolCallIdRef.current = null + editContentParentToolCallIdRef.current.clear() setMessageQueue([]) }, [initialChatId, queryClient]) @@ -541,6 +552,7 @@ export function useChat( streamingFileRef.current = null filePreviewSessionsRef.current.clear() activeFilePreviewToolCallIdRef.current = null + editContentParentToolCallIdRef.current.clear() setMessageQueue([]) }, [isHomePage]) @@ -617,7 +629,7 @@ export function useChat( const reconnectResult = snapshotEvents.length > 0 - ? await attachToExistingStream({ + ? await attachToExistingStreamRef.current({ streamId: activeStreamId, assistantId, expectedGen: gen, @@ -1015,6 +1027,10 @@ export function useChat( sessions.set(id, nextSession) activeFilePreviewToolCallIdRef.current = id streamingFileRef.current = nextSession + const previewToolIdx = toolMap.get(id) + if (previewToolIdx !== undefined && blocks[previewToolIdx].toolCall) { + blocks[previewToolIdx].toolCall!.status = 'executing' + } setStreamingFile(nextSession) break } @@ -1062,11 +1078,19 @@ export function useChat( const opMatch = tc.streamingArgs.match(/"operation"\s*:\s*"(\w+)"/) const op = opMatch?.[1] ?? '' const verb = - op === 'patch' || op === 'update' || op === 'rename' - ? 'Editing' - : op === 'delete' - ? 'Deleting' - : 'Writing' + 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] @@ -1174,7 +1198,20 @@ export function useChat( clientExecutionStartedRef.current.delete(id) } - if (tc.name === WorkspaceFile.id) { + const workspaceFileOperation = + tc.name === WorkspaceFile.id && typeof tc.params?.operation === 'string' + ? tc.params.operation + : undefined + const shouldKeepWorkspacePreviewOpen = + tc.name === WorkspaceFile.id && + (workspaceFileOperation === 'append' || + workspaceFileOperation === 'update' || + workspaceFileOperation === 'patch') + + if ( + (tc.name === WorkspaceFile.id || tc.name === 'edit_content') && + !shouldKeepWorkspacePreviewOpen + ) { filePreviewSessionsRef.current.delete(id) if (activeFilePreviewToolCallIdRef.current === id) { activeFilePreviewToolCallIdRef.current = null @@ -1198,6 +1235,7 @@ export function useChat( setResources((rs) => rs.filter((r) => r.id !== 'streaming-file')) } } + editContentParentToolCallIdRef.current.delete(id) break } @@ -1222,11 +1260,19 @@ export function useChat( if (name === WorkspaceFile.id) { const operation = typeof args?.operation === 'string' ? args.operation : '' const verb = - operation === 'patch' || operation === 'update' || operation === 'rename' - ? 'Editing' - : operation === 'delete' - ? 'Deleting' - : 'Writing' + 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 @@ -1237,6 +1283,25 @@ export function useChat( } } + if (name === 'edit_content') { + const parentToolCallId = + activeFilePreviewToolCallIdRef.current ?? streamingFileRef.current?.toolCallId + const parentIdx = + parentToolCallId !== null && parentToolCallId !== undefined + ? toolMap.get(parentToolCallId) + : undefined + if (parentIdx !== undefined && blocks[parentIdx].toolCall) { + toolMap.set(id, parentIdx) + editContentParentToolCallIdRef.current.set(id, parentToolCallId!) + const tc = blocks[parentIdx].toolCall! + tc.status = 'executing' + tc.result = undefined + tc.error = undefined + flush() + break + } + } + if (!toolMap.has(id)) { toolMap.set(id, blocks.length) blocks.push({ @@ -1608,6 +1673,7 @@ export function useChat( }, [fetchStreamBatch] ) + attachToExistingStreamRef.current = attachToExistingStream const resumeOrFinalize = useCallback( async (opts: { @@ -2054,6 +2120,7 @@ export function useChat( streamingFileRef.current = null filePreviewSessionsRef.current.clear() activeFilePreviewToolCallIdRef.current = null + editContentParentToolCallIdRef.current.clear() setResources((rs) => rs.filter((resource) => resource.id !== 'streaming-file')) const execState = useExecutionStore.getState() diff --git a/apps/sim/app/workspace/[workspaceId]/home/types.ts b/apps/sim/app/workspace/[workspaceId]/home/types.ts index dd5625ea57..88cbb59e6c 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/types.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/types.ts @@ -4,6 +4,7 @@ import { CreateWorkflow, Debug, Deploy, + EditContent, EditWorkflow, FunctionExecute, GetPageContents, @@ -272,6 +273,11 @@ export const TOOL_UI_METADATA: Record = { phaseLabel: 'Resource', phase: 'resource', }, + [EditContent.id]: { + title: 'Writing content', + phaseLabel: 'Resource', + phase: 'resource', + }, [CreateWorkflow.id]: { title: 'Creating workflow', phaseLabel: 'Resource', diff --git a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts index 140a92dd98..5a75fe2ded 100644 --- a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts @@ -3,783 +3,2741 @@ // export interface ToolCatalogEntry { - clientExecutable?: boolean; - executor: "client" | "go" | "sim" | "subagent"; - hidden?: boolean; - id: "agent" | "auth" | "check_deployment_status" | "complete_job" | "context_write" | "crawl_website" | "create_file" | "create_folder" | "create_job" | "create_workflow" | "create_workspace_mcp_server" | "debug" | "delete_file" | "delete_folder" | "delete_workflow" | "delete_workspace_mcp_server" | "deploy" | "deploy_api" | "deploy_chat" | "deploy_mcp" | "download_to_workspace_file" | "edit_workflow" | "file" | "function_execute" | "generate_api_key" | "generate_image" | "generate_visualization" | "get_block_outputs" | "get_block_upstream_references" | "get_deployed_workflow_state" | "get_deployment_version" | "get_execution_summary" | "get_job_logs" | "get_page_contents" | "get_platform_actions" | "get_workflow_data" | "get_workflow_logs" | "glob" | "grep" | "job" | "knowledge" | "knowledge_base" | "list_folders" | "list_user_workspaces" | "list_workspace_mcp_servers" | "manage_credential" | "manage_custom_tool" | "manage_job" | "manage_mcp_tool" | "manage_skill" | "materialize_file" | "move_folder" | "move_workflow" | "oauth_get_auth_link" | "oauth_request_access" | "open_resource" | "read" | "redeploy" | "rename_file" | "rename_workflow" | "research" | "respond" | "revert_to_version" | "run" | "run_block" | "run_from_block" | "run_workflow" | "run_workflow_until_block" | "scrape_page" | "search_documentation" | "search_library_docs" | "search_online" | "search_patterns" | "set_environment_variables" | "set_global_workflow_variables" | "superagent" | "table" | "tool_search_tool_regex" | "update_job_history" | "update_workspace_mcp_server" | "user_memory" | "user_table" | "workflow" | "workspace_file"; - internal?: boolean; - mode: "async" | "sync"; - name: "agent" | "auth" | "check_deployment_status" | "complete_job" | "context_write" | "crawl_website" | "create_file" | "create_folder" | "create_job" | "create_workflow" | "create_workspace_mcp_server" | "debug" | "delete_file" | "delete_folder" | "delete_workflow" | "delete_workspace_mcp_server" | "deploy" | "deploy_api" | "deploy_chat" | "deploy_mcp" | "download_to_workspace_file" | "edit_workflow" | "file" | "function_execute" | "generate_api_key" | "generate_image" | "generate_visualization" | "get_block_outputs" | "get_block_upstream_references" | "get_deployed_workflow_state" | "get_deployment_version" | "get_execution_summary" | "get_job_logs" | "get_page_contents" | "get_platform_actions" | "get_workflow_data" | "get_workflow_logs" | "glob" | "grep" | "job" | "knowledge" | "knowledge_base" | "list_folders" | "list_user_workspaces" | "list_workspace_mcp_servers" | "manage_credential" | "manage_custom_tool" | "manage_job" | "manage_mcp_tool" | "manage_skill" | "materialize_file" | "move_folder" | "move_workflow" | "oauth_get_auth_link" | "oauth_request_access" | "open_resource" | "read" | "redeploy" | "rename_file" | "rename_workflow" | "research" | "respond" | "revert_to_version" | "run" | "run_block" | "run_from_block" | "run_workflow" | "run_workflow_until_block" | "scrape_page" | "search_documentation" | "search_library_docs" | "search_online" | "search_patterns" | "set_environment_variables" | "set_global_workflow_variables" | "superagent" | "table" | "tool_search_tool_regex" | "update_job_history" | "update_workspace_mcp_server" | "user_memory" | "user_table" | "workflow" | "workspace_file"; - parameters: unknown; - requiredPermission?: "admin" | "write"; - requiresConfirmation?: boolean; - resultSchema?: unknown; - subagentId?: "agent" | "auth" | "debug" | "deploy" | "file" | "job" | "knowledge" | "research" | "run" | "superagent" | "table" | "workflow"; + clientExecutable?: boolean + executor: 'client' | 'go' | 'sim' | 'subagent' + hidden?: boolean + id: + | 'agent' + | 'auth' + | 'check_deployment_status' + | 'complete_job' + | 'context_write' + | 'crawl_website' + | 'create_file' + | 'create_folder' + | 'create_job' + | 'create_workflow' + | 'create_workspace_mcp_server' + | 'debug' + | 'delete_file' + | 'delete_folder' + | 'delete_workflow' + | 'delete_workspace_mcp_server' + | 'deploy' + | 'deploy_api' + | 'deploy_chat' + | 'deploy_mcp' + | 'download_to_workspace_file' + | 'edit_content' + | 'edit_workflow' + | 'file' + | 'function_execute' + | 'generate_api_key' + | 'generate_image' + | 'generate_visualization' + | 'get_block_outputs' + | 'get_block_upstream_references' + | 'get_deployed_workflow_state' + | 'get_deployment_version' + | 'get_execution_summary' + | 'get_job_logs' + | 'get_page_contents' + | 'get_platform_actions' + | 'get_workflow_data' + | 'get_workflow_logs' + | 'glob' + | 'grep' + | 'job' + | 'knowledge' + | 'knowledge_base' + | 'list_folders' + | 'list_user_workspaces' + | 'list_workspace_mcp_servers' + | 'manage_credential' + | 'manage_custom_tool' + | 'manage_job' + | 'manage_mcp_tool' + | 'manage_skill' + | 'materialize_file' + | 'move_folder' + | 'move_workflow' + | 'oauth_get_auth_link' + | 'oauth_request_access' + | 'open_resource' + | 'read' + | 'redeploy' + | 'rename_file' + | 'rename_workflow' + | 'research' + | 'respond' + | 'revert_to_version' + | 'run' + | 'run_block' + | 'run_from_block' + | 'run_workflow' + | 'run_workflow_until_block' + | 'scrape_page' + | 'search_documentation' + | 'search_library_docs' + | 'search_online' + | 'search_patterns' + | 'set_environment_variables' + | 'set_global_workflow_variables' + | 'superagent' + | 'table' + | 'tool_search_tool_regex' + | 'update_job_history' + | 'update_workspace_mcp_server' + | 'user_memory' + | 'user_table' + | 'workflow' + | 'workspace_file' + internal?: boolean + mode: 'async' | 'sync' + name: + | 'agent' + | 'auth' + | 'check_deployment_status' + | 'complete_job' + | 'context_write' + | 'crawl_website' + | 'create_file' + | 'create_folder' + | 'create_job' + | 'create_workflow' + | 'create_workspace_mcp_server' + | 'debug' + | 'delete_file' + | 'delete_folder' + | 'delete_workflow' + | 'delete_workspace_mcp_server' + | 'deploy' + | 'deploy_api' + | 'deploy_chat' + | 'deploy_mcp' + | 'download_to_workspace_file' + | 'edit_content' + | 'edit_workflow' + | 'file' + | 'function_execute' + | 'generate_api_key' + | 'generate_image' + | 'generate_visualization' + | 'get_block_outputs' + | 'get_block_upstream_references' + | 'get_deployed_workflow_state' + | 'get_deployment_version' + | 'get_execution_summary' + | 'get_job_logs' + | 'get_page_contents' + | 'get_platform_actions' + | 'get_workflow_data' + | 'get_workflow_logs' + | 'glob' + | 'grep' + | 'job' + | 'knowledge' + | 'knowledge_base' + | 'list_folders' + | 'list_user_workspaces' + | 'list_workspace_mcp_servers' + | 'manage_credential' + | 'manage_custom_tool' + | 'manage_job' + | 'manage_mcp_tool' + | 'manage_skill' + | 'materialize_file' + | 'move_folder' + | 'move_workflow' + | 'oauth_get_auth_link' + | 'oauth_request_access' + | 'open_resource' + | 'read' + | 'redeploy' + | 'rename_file' + | 'rename_workflow' + | 'research' + | 'respond' + | 'revert_to_version' + | 'run' + | 'run_block' + | 'run_from_block' + | 'run_workflow' + | 'run_workflow_until_block' + | 'scrape_page' + | 'search_documentation' + | 'search_library_docs' + | 'search_online' + | 'search_patterns' + | 'set_environment_variables' + | 'set_global_workflow_variables' + | 'superagent' + | 'table' + | 'tool_search_tool_regex' + | 'update_job_history' + | 'update_workspace_mcp_server' + | 'user_memory' + | 'user_table' + | 'workflow' + | 'workspace_file' + parameters: unknown + requiredPermission?: 'admin' | 'write' + requiresConfirmation?: boolean + resultSchema?: unknown + subagentId?: + | 'agent' + | 'auth' + | 'debug' + | 'deploy' + | 'file' + | 'job' + | 'knowledge' + | 'research' + | 'run' + | 'superagent' + | 'table' + | 'workflow' } export const Agent: ToolCatalogEntry = { - id: "agent", - name: "agent", - executor: "subagent", - mode: "async", - parameters: {"properties":{"request":{"description":"What tool/skill/MCP action is needed.","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "agent", + id: 'agent', + name: 'agent', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { + request: { description: 'What tool/skill/MCP action is needed.', type: 'string' }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'agent', internal: true, - requiredPermission: "write", -}; + requiredPermission: 'write', +} export const Auth: ToolCatalogEntry = { - id: "auth", - name: "auth", - executor: "subagent", - mode: "async", - parameters: {"properties":{"request":{"description":"What authentication/credential action is needed.","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "auth", + id: 'auth', + name: 'auth', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { + request: { description: 'What authentication/credential action is needed.', type: 'string' }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'auth', internal: true, -}; +} export const CheckDeploymentStatus: ToolCatalogEntry = { - id: "check_deployment_status", - name: "check_deployment_status", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"workflowId":{"type":"string","description":"Workflow ID to check (defaults to current workflow)"}}}, -}; + id: 'check_deployment_status', + name: 'check_deployment_status', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + workflowId: { + type: 'string', + description: 'Workflow ID to check (defaults to current workflow)', + }, + }, + }, +} export const CompleteJob: ToolCatalogEntry = { - id: "complete_job", - name: "complete_job", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"jobId":{"type":"string","description":"The ID of the job to mark as completed."}},"required":["jobId"]}, -}; + id: 'complete_job', + name: 'complete_job', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + jobId: { type: 'string', description: 'The ID of the job to mark as completed.' }, + }, + required: ['jobId'], + }, +} export const ContextWrite: ToolCatalogEntry = { - id: "context_write", - name: "context_write", - executor: "go", - mode: "sync", - parameters: {"type":"object","properties":{"content":{"type":"string","description":"Full content to write to the file (replaces existing content)"},"file_path":{"type":"string","description":"Path of the file to write (e.g. 'SESSION.md')"}},"required":["file_path","content"]}, -}; + id: 'context_write', + name: 'context_write', + executor: 'go', + mode: 'sync', + parameters: { + type: 'object', + properties: { + content: { + type: 'string', + description: 'Full content to write to the file (replaces existing content)', + }, + file_path: { type: 'string', description: "Path of the file to write (e.g. 'SESSION.md')" }, + }, + required: ['file_path', 'content'], + }, +} export const CrawlWebsite: ToolCatalogEntry = { - id: "crawl_website", - name: "crawl_website", - executor: "go", - mode: "sync", - parameters: {"type":"object","properties":{"exclude_paths":{"type":"array","description":"Skip URLs matching these patterns","items":{"type":"string"}},"include_paths":{"type":"array","description":"Only crawl URLs matching these patterns","items":{"type":"string"}},"limit":{"type":"number","description":"Maximum pages to crawl (default 10, max 50)"},"max_depth":{"type":"number","description":"How deep to follow links (default 2)"},"url":{"type":"string","description":"Starting URL to crawl from"}},"required":["url"]}, -}; + id: 'crawl_website', + name: 'crawl_website', + executor: 'go', + mode: 'sync', + parameters: { + type: 'object', + properties: { + exclude_paths: { + type: 'array', + description: 'Skip URLs matching these patterns', + items: { type: 'string' }, + }, + include_paths: { + type: 'array', + description: 'Only crawl URLs matching these patterns', + items: { type: 'string' }, + }, + limit: { type: 'number', description: 'Maximum pages to crawl (default 10, max 50)' }, + max_depth: { type: 'number', description: 'How deep to follow links (default 2)' }, + url: { type: 'string', description: 'Starting URL to crawl from' }, + }, + required: ['url'], + }, +} export const CreateFile: ToolCatalogEntry = { - id: "create_file", - name: "create_file", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"contentType":{"type":"string","description":"Optional MIME type override. Usually omit and let the system infer from the file extension."},"fileName":{"type":"string","description":"Plain workspace filename including extension, e.g. \"main.py\" or \"report.md\". Must not contain slashes."}},"required":["fileName"]}, - resultSchema: {"type":"object","properties":{"data":{"type":"object","description":"Contains id (the fileId) and name."},"message":{"type":"string","description":"Human-readable outcome."},"success":{"type":"boolean","description":"Whether the file was created."}},"required":["success","message"]}, - requiredPermission: "write", -}; + id: 'create_file', + name: 'create_file', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + contentType: { + type: 'string', + description: + 'Optional MIME type override. Usually omit and let the system infer from the file extension.', + }, + fileName: { + type: 'string', + description: + 'Plain workspace filename including extension, e.g. "main.py" or "report.md". Must not contain slashes.', + }, + }, + required: ['fileName'], + }, + resultSchema: { + type: 'object', + properties: { + data: { type: 'object', description: 'Contains id (the fileId) and name.' }, + message: { type: 'string', description: 'Human-readable outcome.' }, + success: { type: 'boolean', description: 'Whether the file was created.' }, + }, + required: ['success', 'message'], + }, + requiredPermission: 'write', +} export const CreateFolder: ToolCatalogEntry = { - id: "create_folder", - name: "create_folder", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"name":{"type":"string","description":"Folder name."},"parentId":{"type":"string","description":"Optional parent folder ID."},"workspaceId":{"type":"string","description":"Optional workspace ID."}},"required":["name"]}, - requiredPermission: "write", -}; + id: 'create_folder', + name: 'create_folder', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + name: { type: 'string', description: 'Folder name.' }, + parentId: { type: 'string', description: 'Optional parent folder ID.' }, + workspaceId: { type: 'string', description: 'Optional workspace ID.' }, + }, + required: ['name'], + }, + requiredPermission: 'write', +} export const CreateJob: ToolCatalogEntry = { - id: "create_job", - name: "create_job", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"cron":{"type":"string","description":"Cron expression for recurring jobs (e.g., '*/5 * * * *' for every 5 minutes, '0 9 * * *' for daily at 9 AM). Omit for one-time jobs."},"lifecycle":{"type":"string","description":"'persistent' (default) or 'until_complete'. Until_complete jobs stop when complete_job is called after the success condition is met.","enum":["persistent","until_complete"]},"maxRuns":{"type":"integer","description":"Maximum number of executions before the job auto-completes. Safety limit to prevent runaway polling."},"prompt":{"type":"string","description":"The prompt to execute when the job fires. This is sent to the Mothership as a user message."},"successCondition":{"type":"string","description":"What must happen for the job to be considered complete. Used with until_complete lifecycle (e.g., 'John has replied to the partnership email')."},"time":{"type":"string","description":"ISO 8601 datetime for one-time execution or as the start time for a cron schedule (e.g., '2026-03-06T09:00:00'). Include timezone offset or use the timezone parameter."},"timezone":{"type":"string","description":"IANA timezone for the schedule (e.g., 'America/New_York', 'Europe/London'). Defaults to UTC."},"title":{"type":"string","description":"A short, descriptive title for the job (e.g., 'Email Poller', 'Daily Report'). Used as the display name."}},"required":["title","prompt"]}, -}; + id: 'create_job', + name: 'create_job', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + cron: { + type: 'string', + description: + "Cron expression for recurring jobs (e.g., '*/5 * * * *' for every 5 minutes, '0 9 * * *' for daily at 9 AM). Omit for one-time jobs.", + }, + lifecycle: { + type: 'string', + description: + "'persistent' (default) or 'until_complete'. Until_complete jobs stop when complete_job is called after the success condition is met.", + enum: ['persistent', 'until_complete'], + }, + maxRuns: { + type: 'integer', + description: + 'Maximum number of executions before the job auto-completes. Safety limit to prevent runaway polling.', + }, + prompt: { + type: 'string', + description: + 'The prompt to execute when the job fires. This is sent to the Mothership as a user message.', + }, + successCondition: { + type: 'string', + description: + "What must happen for the job to be considered complete. Used with until_complete lifecycle (e.g., 'John has replied to the partnership email').", + }, + time: { + type: 'string', + description: + "ISO 8601 datetime for one-time execution or as the start time for a cron schedule (e.g., '2026-03-06T09:00:00'). Include timezone offset or use the timezone parameter.", + }, + timezone: { + type: 'string', + description: + "IANA timezone for the schedule (e.g., 'America/New_York', 'Europe/London'). Defaults to UTC.", + }, + title: { + type: 'string', + description: + "A short, descriptive title for the job (e.g., 'Email Poller', 'Daily Report'). Used as the display name.", + }, + }, + required: ['title', 'prompt'], + }, +} export const CreateWorkflow: ToolCatalogEntry = { - id: "create_workflow", - name: "create_workflow", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"description":{"type":"string","description":"Optional workflow description."},"folderId":{"type":"string","description":"Optional folder ID."},"name":{"type":"string","description":"Workflow name."},"workspaceId":{"type":"string","description":"Optional workspace ID."}},"required":["name"]}, - requiredPermission: "write", -}; + id: 'create_workflow', + name: 'create_workflow', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + description: { type: 'string', description: 'Optional workflow description.' }, + folderId: { type: 'string', description: 'Optional folder ID.' }, + name: { type: 'string', description: 'Workflow name.' }, + workspaceId: { type: 'string', description: 'Optional workspace ID.' }, + }, + required: ['name'], + }, + requiredPermission: 'write', +} export const CreateWorkspaceMcpServer: ToolCatalogEntry = { - id: "create_workspace_mcp_server", - name: "create_workspace_mcp_server", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"description":{"type":"string","description":"Optional description for the server"},"name":{"type":"string","description":"Required: server name"},"workspaceId":{"type":"string","description":"Workspace ID (defaults to current workspace)"}},"required":["name"]}, + id: 'create_workspace_mcp_server', + name: 'create_workspace_mcp_server', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + description: { type: 'string', description: 'Optional description for the server' }, + name: { type: 'string', description: 'Required: server name' }, + workspaceId: { type: 'string', description: 'Workspace ID (defaults to current workspace)' }, + }, + required: ['name'], + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const Debug: ToolCatalogEntry = { - id: "debug", - name: "debug", - executor: "subagent", - mode: "async", - parameters: {"properties":{"context":{"description":"Pre-gathered context: workflow state JSON, block schemas, error logs. The debug agent will skip re-reading anything included here.","type":"string"},"request":{"description":"What to debug. Include error messages, block IDs, and any context about the failure.","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "debug", + id: 'debug', + name: 'debug', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { + context: { + description: + 'Pre-gathered context: workflow state JSON, block schemas, error logs. The debug agent will skip re-reading anything included here.', + type: 'string', + }, + request: { + description: + 'What to debug. Include error messages, block IDs, and any context about the failure.', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'debug', internal: true, -}; +} export const DeleteFile: ToolCatalogEntry = { - id: "delete_file", - name: "delete_file", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"fileId":{"type":"string","description":"Canonical workspace file ID of the file to delete."}},"required":["fileId"]}, - resultSchema: {"type":"object","properties":{"message":{"type":"string","description":"Human-readable outcome."},"success":{"type":"boolean","description":"Whether the delete succeeded."}},"required":["success","message"]}, - requiredPermission: "write", -}; + id: 'delete_file', + name: 'delete_file', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + fileId: { type: 'string', description: 'Canonical workspace file ID of the file to delete.' }, + }, + required: ['fileId'], + }, + resultSchema: { + type: 'object', + properties: { + message: { type: 'string', description: 'Human-readable outcome.' }, + success: { type: 'boolean', description: 'Whether the delete succeeded.' }, + }, + required: ['success', 'message'], + }, + requiredPermission: 'write', +} export const DeleteFolder: ToolCatalogEntry = { - id: "delete_folder", - name: "delete_folder", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"folderId":{"type":"string","description":"The folder ID to delete."}},"required":["folderId"]}, + id: 'delete_folder', + name: 'delete_folder', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { folderId: { type: 'string', description: 'The folder ID to delete.' } }, + required: ['folderId'], + }, requiresConfirmation: true, - requiredPermission: "write", -}; + requiredPermission: 'write', +} export const DeleteWorkflow: ToolCatalogEntry = { - id: "delete_workflow", - name: "delete_workflow", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"workflowId":{"type":"string","description":"The workflow ID to delete."}},"required":["workflowId"]}, + id: 'delete_workflow', + name: 'delete_workflow', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { workflowId: { type: 'string', description: 'The workflow ID to delete.' } }, + required: ['workflowId'], + }, requiresConfirmation: true, - requiredPermission: "write", -}; + requiredPermission: 'write', +} export const DeleteWorkspaceMcpServer: ToolCatalogEntry = { - id: "delete_workspace_mcp_server", - name: "delete_workspace_mcp_server", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"serverId":{"type":"string","description":"Required: the MCP server ID to delete"}},"required":["serverId"]}, + id: 'delete_workspace_mcp_server', + name: 'delete_workspace_mcp_server', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + serverId: { type: 'string', description: 'Required: the MCP server ID to delete' }, + }, + required: ['serverId'], + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const Deploy: ToolCatalogEntry = { - id: "deploy", - name: "deploy", - executor: "subagent", - mode: "async", - parameters: {"properties":{"request":{"description":"Detailed deployment instructions. Include deployment type (api/chat) and ALL user-specified options: identifier, title, description, authType, password, allowedEmails, welcomeMessage, outputConfigs (block outputs to display).","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "deploy", + id: 'deploy', + name: 'deploy', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { + request: { + description: + 'Detailed deployment instructions. Include deployment type (api/chat) and ALL user-specified options: identifier, title, description, authType, password, allowedEmails, welcomeMessage, outputConfigs (block outputs to display).', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'deploy', internal: true, -}; +} export const DeployApi: ToolCatalogEntry = { - id: "deploy_api", - name: "deploy_api", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"action":{"type":"string","description":"Whether to deploy or undeploy the API endpoint","enum":["deploy","undeploy"],"default":"deploy"},"workflowId":{"type":"string","description":"Workflow ID to deploy (required in workspace context)"}}}, + id: 'deploy_api', + name: 'deploy_api', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + action: { + type: 'string', + description: 'Whether to deploy or undeploy the API endpoint', + enum: ['deploy', 'undeploy'], + default: 'deploy', + }, + workflowId: { + type: 'string', + description: 'Workflow ID to deploy (required in workspace context)', + }, + }, + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const DeployChat: ToolCatalogEntry = { - id: "deploy_chat", - name: "deploy_chat", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"action":{"type":"string","description":"Whether to deploy or undeploy the chat interface","enum":["deploy","undeploy"],"default":"deploy"},"allowedEmails":{"type":"array","description":"List of allowed emails/domains for email or SSO auth","items":{"type":"string"}},"authType":{"type":"string","description":"Authentication type: public, password, email, or sso","enum":["public","password","email","sso"],"default":"public"},"description":{"type":"string","description":"Optional description for the chat"},"identifier":{"type":"string","description":"URL slug for the chat (lowercase letters, numbers, hyphens only)"},"outputConfigs":{"type":"array","description":"Output configurations specifying which block outputs to display in chat","items":{"type":"object","properties":{"blockId":{"type":"string","description":"The block UUID"},"path":{"type":"string","description":"The output path (e.g. 'response', 'response.content')"}},"required":["blockId","path"]}},"password":{"type":"string","description":"Password for password-protected chats"},"title":{"type":"string","description":"Display title for the chat interface"},"welcomeMessage":{"type":"string","description":"Welcome message shown to users"},"workflowId":{"type":"string","description":"Workflow ID to deploy (required in workspace context)"}}}, + id: 'deploy_chat', + name: 'deploy_chat', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + action: { + type: 'string', + description: 'Whether to deploy or undeploy the chat interface', + enum: ['deploy', 'undeploy'], + default: 'deploy', + }, + allowedEmails: { + type: 'array', + description: 'List of allowed emails/domains for email or SSO auth', + items: { type: 'string' }, + }, + authType: { + type: 'string', + description: 'Authentication type: public, password, email, or sso', + enum: ['public', 'password', 'email', 'sso'], + default: 'public', + }, + description: { type: 'string', description: 'Optional description for the chat' }, + identifier: { + type: 'string', + description: 'URL slug for the chat (lowercase letters, numbers, hyphens only)', + }, + outputConfigs: { + type: 'array', + description: 'Output configurations specifying which block outputs to display in chat', + items: { + type: 'object', + properties: { + blockId: { type: 'string', description: 'The block UUID' }, + path: { + type: 'string', + description: "The output path (e.g. 'response', 'response.content')", + }, + }, + required: ['blockId', 'path'], + }, + }, + password: { type: 'string', description: 'Password for password-protected chats' }, + title: { type: 'string', description: 'Display title for the chat interface' }, + welcomeMessage: { type: 'string', description: 'Welcome message shown to users' }, + workflowId: { + type: 'string', + description: 'Workflow ID to deploy (required in workspace context)', + }, + }, + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const DeployMcp: ToolCatalogEntry = { - id: "deploy_mcp", - name: "deploy_mcp", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"parameterDescriptions":{"type":"array","description":"Array of parameter descriptions for the tool","items":{"type":"object","properties":{"description":{"type":"string","description":"Parameter description"},"name":{"type":"string","description":"Parameter name"}},"required":["name","description"]}},"serverId":{"type":"string","description":"Required: server ID from list_workspace_mcp_servers"},"toolDescription":{"type":"string","description":"Description for the MCP tool"},"toolName":{"type":"string","description":"Name for the MCP tool (defaults to workflow name)"},"workflowId":{"type":"string","description":"Workflow ID (defaults to active workflow)"}},"required":["serverId"]}, + id: 'deploy_mcp', + name: 'deploy_mcp', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + parameterDescriptions: { + type: 'array', + description: 'Array of parameter descriptions for the tool', + items: { + type: 'object', + properties: { + description: { type: 'string', description: 'Parameter description' }, + name: { type: 'string', description: 'Parameter name' }, + }, + required: ['name', 'description'], + }, + }, + serverId: { + type: 'string', + description: 'Required: server ID from list_workspace_mcp_servers', + }, + toolDescription: { type: 'string', description: 'Description for the MCP tool' }, + toolName: { + type: 'string', + description: 'Name for the MCP tool (defaults to workflow name)', + }, + workflowId: { type: 'string', description: 'Workflow ID (defaults to active workflow)' }, + }, + required: ['serverId'], + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const DownloadToWorkspaceFile: ToolCatalogEntry = { - id: "download_to_workspace_file", - name: "download_to_workspace_file", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"fileName":{"type":"string","description":"Optional workspace file name to save as. If omitted, the name is inferred from the response or URL."},"url":{"type":"string","description":"Direct URL of the file to download, such as an image CDN URL ending in .png or .jpg"}},"required":["url"]}, - requiredPermission: "write", -}; + id: 'download_to_workspace_file', + name: 'download_to_workspace_file', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + fileName: { + type: 'string', + description: + 'Optional workspace file name to save as. If omitted, the name is inferred from the response or URL.', + }, + url: { + type: 'string', + description: + 'Direct URL of the file to download, such as an image CDN URL ending in .png or .jpg', + }, + }, + required: ['url'], + }, + requiredPermission: 'write', +} export const EditWorkflow: ToolCatalogEntry = { - id: "edit_workflow", - name: "edit_workflow", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"operations":{"type":"array","description":"Array of edit operations","items":{"type":"object","properties":{"block_id":{"type":"string","description":"Block ID for the operation. For add operations, this will be the desired ID for the new block."},"operation_type":{"type":"string","description":"Type of operation to perform","enum":["add","edit","delete","insert_into_subflow","extract_from_subflow"]},"params":{"type":"object","description":"Parameters for the operation. \nFor edit: {\"inputs\": {\"temperature\": 0.5}} NOT {\"subBlocks\": {\"temperature\": {\"value\": 0.5}}}\nFor add: {\"type\": \"agent\", \"name\": \"My Agent\", \"inputs\": {\"model\": \"gpt-4o\"}}\nFor delete: {} (empty object)"}},"required":["operation_type","block_id","params"]}},"workflowId":{"type":"string","description":"Optional workflow ID to edit. If not provided, uses the current workflow in context."}},"required":["operations"]}, - requiredPermission: "write", -}; + id: 'edit_workflow', + name: 'edit_workflow', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + operations: { + type: 'array', + description: 'Array of edit operations', + items: { + type: 'object', + properties: { + block_id: { + type: 'string', + description: + 'Block ID for the operation. For add operations, this will be the desired ID for the new block.', + }, + operation_type: { + type: 'string', + description: 'Type of operation to perform', + enum: ['add', 'edit', 'delete', 'insert_into_subflow', 'extract_from_subflow'], + }, + params: { + type: 'object', + description: + 'Parameters for the operation. \nFor edit: {"inputs": {"temperature": 0.5}} NOT {"subBlocks": {"temperature": {"value": 0.5}}}\nFor add: {"type": "agent", "name": "My Agent", "inputs": {"model": "gpt-4o"}}\nFor delete: {} (empty object)', + }, + }, + required: ['operation_type', 'block_id', 'params'], + }, + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to edit. If not provided, uses the current workflow in context.', + }, + }, + required: ['operations'], + }, + requiredPermission: 'write', +} export const File: ToolCatalogEntry = { - id: "file", - name: "file", - executor: "subagent", - mode: "async", - parameters: {"type":"object"}, - subagentId: "file", + id: 'file', + name: 'file', + executor: 'subagent', + mode: 'async', + parameters: { type: 'object' }, + subagentId: 'file', internal: true, -}; +} export const FunctionExecute: ToolCatalogEntry = { - id: "function_execute", - name: "function_execute", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"code":{"type":"string","description":"Code to execute. For JS: raw statements auto-wrapped in async context. For Python: full script. For shell: bash script with access to pre-installed CLI tools and workspace env vars as $VAR_NAME."},"inputFiles":{"type":"array","description":"Canonical workspace file IDs to mount in the sandbox. Discover IDs via read(\"files/{name}/meta.json\") or glob(\"files/by-id/*/meta.json\"). Mounted path: /home/user/files/{fileId}/{originalName}. Example: [\"wf_123\"]","items":{"type":"string"}},"inputTables":{"type":"array","description":"Table IDs to mount as CSV files in the sandbox. Each table appears at /home/user/tables/{tableId}.csv with a header row. Example: [\"tbl_abc123\"]","items":{"type":"string"}},"language":{"type":"string","description":"Execution language.","enum":["javascript","python","shell"]},"outputFormat":{"type":"string","description":"Format for outputPath. Determines how the code result is serialized. If omitted, inferred from outputPath file extension.","enum":["json","csv","txt","md","html"]},"outputMimeType":{"type":"string","description":"MIME type for outputSandboxPath export. Required for binary files: image/png, image/jpeg, application/pdf, etc. Omit for text files."},"outputPath":{"type":"string","description":"Pipe output directly to a NEW workspace file instead of returning in context. ALWAYS use this instead of a separate workspace_file write call. Use a flat path like \"files/result.json\" — nested paths are not supported."},"outputSandboxPath":{"type":"string","description":"Path to a file created inside the sandbox that should be exported to the workspace. Use together with outputPath."},"outputTable":{"type":"string","description":"Table ID to overwrite with the code's return value. Code MUST return an array of objects where keys match column names. All existing rows are replaced. Example: \"tbl_abc123\""}},"required":["code"]}, - requiredPermission: "write", -}; + id: 'function_execute', + name: 'function_execute', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + code: { + type: 'string', + description: + 'Code to execute. For JS: raw statements auto-wrapped in async context. For Python: full script. For shell: bash script with access to pre-installed CLI tools and workspace env vars as $VAR_NAME.', + }, + inputFiles: { + type: 'array', + description: + 'Canonical workspace file IDs to mount in the sandbox. Discover IDs via read("files/{name}/meta.json") or glob("files/by-id/*/meta.json"). Mounted path: /home/user/files/{fileId}/{originalName}. Example: ["wf_123"]', + items: { type: 'string' }, + }, + inputTables: { + type: 'array', + description: + 'Table IDs to mount as CSV files in the sandbox. Each table appears at /home/user/tables/{tableId}.csv with a header row. Example: ["tbl_abc123"]', + items: { type: 'string' }, + }, + language: { + type: 'string', + description: 'Execution language.', + enum: ['javascript', 'python', 'shell'], + }, + outputFormat: { + type: 'string', + description: + 'Format for outputPath. Determines how the code result is serialized. If omitted, inferred from outputPath file extension.', + enum: ['json', 'csv', 'txt', 'md', 'html'], + }, + outputMimeType: { + type: 'string', + description: + 'MIME type for outputSandboxPath export. Required for binary files: image/png, image/jpeg, application/pdf, etc. Omit for text files.', + }, + outputPath: { + type: 'string', + description: + 'Pipe output directly to a NEW workspace file instead of returning in context. ALWAYS use this instead of a separate workspace_file write call. Use a flat path like "files/result.json" — nested paths are not supported.', + }, + outputSandboxPath: { + type: 'string', + description: + 'Path to a file created inside the sandbox that should be exported to the workspace. Use together with outputPath.', + }, + outputTable: { + type: 'string', + description: + 'Table ID to overwrite with the code\'s return value. Code MUST return an array of objects where keys match column names. All existing rows are replaced. Example: "tbl_abc123"', + }, + }, + required: ['code'], + }, + requiredPermission: 'write', +} export const GenerateApiKey: ToolCatalogEntry = { - id: "generate_api_key", - name: "generate_api_key", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"name":{"type":"string","description":"A descriptive name for the API key (e.g., 'production-key', 'dev-testing')."},"workspaceId":{"type":"string","description":"Optional workspace ID. Defaults to user's default workspace."}},"required":["name"]}, + id: 'generate_api_key', + name: 'generate_api_key', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + name: { + type: 'string', + description: "A descriptive name for the API key (e.g., 'production-key', 'dev-testing').", + }, + workspaceId: { + type: 'string', + description: "Optional workspace ID. Defaults to user's default workspace.", + }, + }, + required: ['name'], + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const GenerateImage: ToolCatalogEntry = { - id: "generate_image", - name: "generate_image", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"aspectRatio":{"type":"string","description":"Aspect ratio for the generated image.","enum":["1:1","16:9","9:16","4:3","3:4"]},"fileName":{"type":"string","description":"Output file name. Defaults to \"generated-image.png\". Workspace files are flat, so pass a plain file name, not a nested path."},"overwriteFileId":{"type":"string","description":"If provided, overwrites the existing workspace file with this ID instead of creating a new file. Use this when the user asks to update or redo a previously generated image. The file ID is returned by previous generate_image or generate_visualization calls (fileId field), or can be found via read(\"files/by-id/{fileId}/meta.json\")."},"prompt":{"type":"string","description":"Detailed text description of the image to generate, or editing instructions when used with editFileId."},"referenceFileIds":{"type":"array","description":"File IDs of workspace images to include as context for the generation. All images are sent alongside the prompt. Use for: editing a single image (1 file), compositing multiple images together (2+ files), style transfer, face swapping, etc. Order matters — list the primary/base image first.","items":{"type":"string"}}},"required":["prompt"]}, - requiredPermission: "write", -}; + id: 'generate_image', + name: 'generate_image', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + aspectRatio: { + type: 'string', + description: 'Aspect ratio for the generated image.', + enum: ['1:1', '16:9', '9:16', '4:3', '3:4'], + }, + fileName: { + type: 'string', + description: + 'Output file name. Defaults to "generated-image.png". Workspace files are flat, so pass a plain file name, not a nested path.', + }, + overwriteFileId: { + type: 'string', + description: + 'If provided, overwrites the existing workspace file with this ID instead of creating a new file. Use this when the user asks to update or redo a previously generated image. The file ID is returned by previous generate_image or generate_visualization calls (fileId field), or can be found via read("files/by-id/{fileId}/meta.json").', + }, + prompt: { + type: 'string', + description: + 'Detailed text description of the image to generate, or editing instructions when used with editFileId.', + }, + referenceFileIds: { + type: 'array', + description: + 'File IDs of workspace images to include as context for the generation. All images are sent alongside the prompt. Use for: editing a single image (1 file), compositing multiple images together (2+ files), style transfer, face swapping, etc. Order matters — list the primary/base image first.', + items: { type: 'string' }, + }, + }, + required: ['prompt'], + }, + requiredPermission: 'write', +} export const GenerateVisualization: ToolCatalogEntry = { - id: "generate_visualization", - name: "generate_visualization", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"code":{"type":"string","description":"Python code that generates a visualization using matplotlib. MUST call plt.savefig('/home/user/output.png', dpi=150, bbox_inches='tight') to produce output."},"fileName":{"type":"string","description":"Output file name. Defaults to \"chart.png\". Workspace files are flat, so pass a plain file name, not a nested path."},"inputFiles":{"type":"array","description":"Canonical workspace file IDs to mount in the sandbox. Discover IDs via read(\"files/{name}/meta.json\") or glob(\"files/by-id/*/meta.json\"). Mounted path: /home/user/files/{fileId}/{originalName}.","items":{"type":"string"}},"inputTables":{"type":"array","description":"Table IDs to mount as CSV files in the sandbox. Each table appears at /home/user/tables/{tableId}.csv with a header row. Read with pandas: pd.read_csv('/home/user/tables/tbl_xxx.csv')","items":{"type":"string"}},"overwriteFileId":{"type":"string","description":"If provided, overwrites the existing workspace file with this ID instead of creating a new file. Use this when the user asks to update or redo a previously generated chart. The file ID is returned by previous generate_visualization or generate_image calls (fileId field), or can be found via read(\"files/by-id/{fileId}/meta.json\")."}},"required":["code"]}, - requiredPermission: "write", -}; + id: 'generate_visualization', + name: 'generate_visualization', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + code: { + type: 'string', + description: + "Python code that generates a visualization using matplotlib. MUST call plt.savefig('/home/user/output.png', dpi=150, bbox_inches='tight') to produce output.", + }, + fileName: { + type: 'string', + description: + 'Output file name. Defaults to "chart.png". Workspace files are flat, so pass a plain file name, not a nested path.', + }, + inputFiles: { + type: 'array', + description: + 'Canonical workspace file IDs to mount in the sandbox. Discover IDs via read("files/{name}/meta.json") or glob("files/by-id/*/meta.json"). Mounted path: /home/user/files/{fileId}/{originalName}.', + items: { type: 'string' }, + }, + inputTables: { + type: 'array', + description: + "Table IDs to mount as CSV files in the sandbox. Each table appears at /home/user/tables/{tableId}.csv with a header row. Read with pandas: pd.read_csv('/home/user/tables/tbl_xxx.csv')", + items: { type: 'string' }, + }, + overwriteFileId: { + type: 'string', + description: + 'If provided, overwrites the existing workspace file with this ID instead of creating a new file. Use this when the user asks to update or redo a previously generated chart. The file ID is returned by previous generate_visualization or generate_image calls (fileId field), or can be found via read("files/by-id/{fileId}/meta.json").', + }, + }, + required: ['code'], + }, + requiredPermission: 'write', +} export const GetBlockOutputs: ToolCatalogEntry = { - id: "get_block_outputs", - name: "get_block_outputs", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"blockIds":{"type":"array","description":"Optional array of block UUIDs. If provided, returns outputs only for those blocks. If not provided, returns outputs for all blocks in the workflow.","items":{"type":"string"}},"workflowId":{"type":"string","description":"Optional workflow ID. If not provided, uses the current workflow in context."}}}, -}; + id: 'get_block_outputs', + name: 'get_block_outputs', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + blockIds: { + type: 'array', + description: + 'Optional array of block UUIDs. If provided, returns outputs only for those blocks. If not provided, returns outputs for all blocks in the workflow.', + items: { type: 'string' }, + }, + workflowId: { + type: 'string', + description: 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + }, +} export const GetBlockUpstreamReferences: ToolCatalogEntry = { - id: "get_block_upstream_references", - name: "get_block_upstream_references", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"blockIds":{"type":"array","description":"Required array of block UUIDs (minimum 1). Returns what each block can reference based on its position in the workflow graph.","items":{"type":"string"}},"workflowId":{"type":"string","description":"Optional workflow ID. If not provided, uses the current workflow in context."}},"required":["blockIds"]}, -}; + id: 'get_block_upstream_references', + name: 'get_block_upstream_references', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + blockIds: { + type: 'array', + description: + 'Required array of block UUIDs (minimum 1). Returns what each block can reference based on its position in the workflow graph.', + items: { type: 'string' }, + }, + workflowId: { + type: 'string', + description: 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + required: ['blockIds'], + }, +} export const GetDeployedWorkflowState: ToolCatalogEntry = { - id: "get_deployed_workflow_state", - name: "get_deployed_workflow_state", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"workflowId":{"type":"string","description":"Optional workflow ID. If not provided, uses the current workflow in context."}}}, -}; + id: 'get_deployed_workflow_state', + name: 'get_deployed_workflow_state', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + workflowId: { + type: 'string', + description: 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + }, +} export const GetDeploymentVersion: ToolCatalogEntry = { - id: "get_deployment_version", - name: "get_deployment_version", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"version":{"type":"number","description":"The deployment version number"},"workflowId":{"type":"string","description":"The workflow ID"}},"required":["workflowId","version"]}, -}; + id: 'get_deployment_version', + name: 'get_deployment_version', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + version: { type: 'number', description: 'The deployment version number' }, + workflowId: { type: 'string', description: 'The workflow ID' }, + }, + required: ['workflowId', 'version'], + }, +} export const GetExecutionSummary: ToolCatalogEntry = { - id: "get_execution_summary", - name: "get_execution_summary", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"limit":{"type":"number","description":"Max number of executions to return (default: 10, max: 20)."},"status":{"type":"string","description":"Filter by status: 'success', 'error', or 'all' (default: 'all').","enum":["success","error","all"]},"workflowId":{"type":"string","description":"Optional workflow ID. If omitted, returns executions across all workflows in the workspace."},"workspaceId":{"type":"string","description":"Workspace ID to scope executions to."}},"required":["workspaceId"]}, -}; + id: 'get_execution_summary', + name: 'get_execution_summary', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + limit: { + type: 'number', + description: 'Max number of executions to return (default: 10, max: 20).', + }, + status: { + type: 'string', + description: "Filter by status: 'success', 'error', or 'all' (default: 'all').", + enum: ['success', 'error', 'all'], + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID. If omitted, returns executions across all workflows in the workspace.', + }, + workspaceId: { type: 'string', description: 'Workspace ID to scope executions to.' }, + }, + required: ['workspaceId'], + }, +} export const GetJobLogs: ToolCatalogEntry = { - id: "get_job_logs", - name: "get_job_logs", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"executionId":{"type":"string","description":"Optional execution ID for a specific run."},"includeDetails":{"type":"boolean","description":"Include tool calls, outputs, and cost details."},"jobId":{"type":"string","description":"The job (schedule) ID to get logs for."},"limit":{"type":"number","description":"Max number of entries (default: 3, max: 5)"}},"required":["jobId"]}, -}; + id: 'get_job_logs', + name: 'get_job_logs', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + executionId: { type: 'string', description: 'Optional execution ID for a specific run.' }, + includeDetails: { + type: 'boolean', + description: 'Include tool calls, outputs, and cost details.', + }, + jobId: { type: 'string', description: 'The job (schedule) ID to get logs for.' }, + limit: { type: 'number', description: 'Max number of entries (default: 3, max: 5)' }, + }, + required: ['jobId'], + }, +} export const GetPageContents: ToolCatalogEntry = { - id: "get_page_contents", - name: "get_page_contents", - executor: "go", - mode: "sync", - parameters: {"type":"object","properties":{"include_highlights":{"type":"boolean","description":"Include key highlights (default false)"},"include_summary":{"type":"boolean","description":"Include AI-generated summary (default false)"},"include_text":{"type":"boolean","description":"Include full page text (default true)"},"urls":{"type":"array","description":"URLs to get content from (max 10)","items":{"type":"string"}}},"required":["urls"]}, -}; + id: 'get_page_contents', + name: 'get_page_contents', + executor: 'go', + mode: 'sync', + parameters: { + type: 'object', + properties: { + include_highlights: { + type: 'boolean', + description: 'Include key highlights (default false)', + }, + include_summary: { + type: 'boolean', + description: 'Include AI-generated summary (default false)', + }, + include_text: { type: 'boolean', description: 'Include full page text (default true)' }, + urls: { + type: 'array', + description: 'URLs to get content from (max 10)', + items: { type: 'string' }, + }, + }, + required: ['urls'], + }, +} export const GetPlatformActions: ToolCatalogEntry = { - id: "get_platform_actions", - name: "get_platform_actions", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{}}, -}; + id: 'get_platform_actions', + name: 'get_platform_actions', + executor: 'sim', + mode: 'async', + parameters: { type: 'object', properties: {} }, +} export const GetWorkflowData: ToolCatalogEntry = { - id: "get_workflow_data", - name: "get_workflow_data", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"data_type":{"type":"string","description":"The type of workflow data to retrieve","enum":["global_variables","custom_tools","mcp_tools","files"]},"workflowId":{"type":"string","description":"Optional workflow ID. If not provided, uses the current workflow in context."}},"required":["data_type"]}, -}; + id: 'get_workflow_data', + name: 'get_workflow_data', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + data_type: { + type: 'string', + description: 'The type of workflow data to retrieve', + enum: ['global_variables', 'custom_tools', 'mcp_tools', 'files'], + }, + workflowId: { + type: 'string', + description: 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + required: ['data_type'], + }, +} export const GetWorkflowLogs: ToolCatalogEntry = { - id: "get_workflow_logs", - name: "get_workflow_logs", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"executionId":{"type":"string","description":"Optional execution ID to get logs for a specific execution. Use with get_execution_summary to find execution IDs first."},"includeDetails":{"type":"boolean","description":"Include detailed info"},"limit":{"type":"number","description":"Max number of entries (hard limit: 3)"},"workflowId":{"type":"string","description":"Optional workflow ID. If not provided, uses the current workflow in context."}}}, -}; + id: 'get_workflow_logs', + name: 'get_workflow_logs', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + executionId: { + type: 'string', + description: + 'Optional execution ID to get logs for a specific execution. Use with get_execution_summary to find execution IDs first.', + }, + includeDetails: { type: 'boolean', description: 'Include detailed info' }, + limit: { type: 'number', description: 'Max number of entries (hard limit: 3)' }, + workflowId: { + type: 'string', + description: 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + }, +} export const Glob: ToolCatalogEntry = { - id: "glob", - name: "glob", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"pattern":{"type":"string","description":"Glob pattern to match file paths. Supports * (any segment) and ** (any depth)."},"title":{"type":"string","description":"Short human-readable label shown in the UI while this search runs (e.g. 'Finding workflow configs', 'Listing knowledge bases')."}},"required":["pattern"]}, -}; + id: 'glob', + name: 'glob', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + pattern: { + type: 'string', + description: + 'Glob pattern to match file paths. Supports * (any segment) and ** (any depth).', + }, + title: { + type: 'string', + description: + "Short human-readable label shown in the UI while this search runs (e.g. 'Finding workflow configs', 'Listing knowledge bases').", + }, + }, + required: ['pattern'], + }, +} export const Grep: ToolCatalogEntry = { - id: "grep", - name: "grep", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"context":{"type":"number","description":"Number of lines to show before and after each match. Only applies to output_mode 'content'."},"ignoreCase":{"type":"boolean","description":"Case insensitive search (default false)."},"lineNumbers":{"type":"boolean","description":"Include line numbers in output (default true). Only applies to output_mode 'content'."},"maxResults":{"type":"number","description":"Maximum number of matches to return (default 50)."},"output_mode":{"type":"string","description":"Output mode: 'content' shows matching lines (default), 'files_with_matches' shows only file paths, 'count' shows match counts per file.","enum":["content","files_with_matches","count"]},"path":{"type":"string","description":"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":{"type":"string","description":"Short human-readable label shown in the UI while this search runs (e.g. 'Searching Slack integrations', 'Finding deployed workflows')."}},"required":["pattern"]}, -}; + id: 'grep', + name: 'grep', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + context: { + type: 'number', + description: + "Number of lines to show before and after each match. Only applies to output_mode 'content'.", + }, + ignoreCase: { type: 'boolean', description: 'Case insensitive search (default false).' }, + lineNumbers: { + type: 'boolean', + description: + "Include line numbers in output (default true). Only applies to output_mode 'content'.", + }, + maxResults: { + type: 'number', + description: 'Maximum number of matches to return (default 50).', + }, + output_mode: { + type: 'string', + description: + "Output mode: 'content' shows matching lines (default), 'files_with_matches' shows only file paths, 'count' shows match counts per file.", + enum: ['content', 'files_with_matches', 'count'], + }, + path: { + type: 'string', + description: + "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: { + type: 'string', + description: + "Short human-readable label shown in the UI while this search runs (e.g. 'Searching Slack integrations', 'Finding deployed workflows').", + }, + }, + required: ['pattern'], + }, +} export const Job: ToolCatalogEntry = { - id: "job", - name: "job", - executor: "subagent", - mode: "async", - parameters: {"properties":{"request":{"description":"What job action is needed.","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "job", + id: 'job', + name: 'job', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { request: { description: 'What job action is needed.', type: 'string' } }, + required: ['request'], + type: 'object', + }, + subagentId: 'job', internal: true, -}; +} export const Knowledge: ToolCatalogEntry = { - id: "knowledge", - name: "knowledge", - executor: "subagent", - mode: "async", - parameters: {"properties":{"request":{"description":"What knowledge base action is needed.","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "knowledge", + id: 'knowledge', + name: 'knowledge', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { + request: { description: 'What knowledge base action is needed.', type: 'string' }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'knowledge', internal: true, -}; +} export const KnowledgeBase: ToolCatalogEntry = { - id: "knowledge_base", - name: "knowledge_base", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"args":{"type":"object","description":"Arguments for the operation","properties":{"apiKey":{"type":"string","description":"API key for API-key-based connectors (required when connector auth mode is apiKey)"},"chunkingConfig":{"type":"object","description":"Chunking configuration (optional for 'create')","properties":{"maxSize":{"type":"number","description":"Maximum chunk size (100-4000, default: 1024)","default":1024},"minSize":{"type":"number","description":"Minimum chunk size (1-2000, default: 1)","default":1},"overlap":{"type":"number","description":"Overlap between chunks (0-500, default: 200)","default":200}}},"connectorId":{"type":"string","description":"Connector ID (required for update_connector, delete_connector, sync_connector)"},"connectorStatus":{"type":"string","description":"Connector status (optional for update_connector)","enum":["active","paused"]},"connectorType":{"type":"string","description":"Connector type from registry, e.g. 'confluence', 'google_drive', 'notion' (required for add_connector). Read knowledgebases/connectors/{type}.json for the config schema."},"credentialId":{"type":"string","description":"OAuth credential ID from environment/credentials.json (required for OAuth connectors)"},"description":{"type":"string","description":"Description of the knowledge base (optional for 'create')"},"disabledTagIds":{"type":"array","description":"Tag definition IDs to opt out of (optional for add_connector). See tagDefinitions in the connector schema."},"documentId":{"type":"string","description":"Document ID (required for delete_document, update_document)"},"enabled":{"type":"boolean","description":"Enable/disable a document (optional for update_document)"},"fileId":{"type":"string","description":"Canonical workspace file ID to add as a document (preferred for add_file). Discover via read(\"files/{name}/meta.json\") or glob(\"files/by-id/*/meta.json\")."},"filePath":{"type":"string","description":"Legacy workspace file reference for add_file. Prefer fileId."},"filename":{"type":"string","description":"New filename for a document (optional for update_document)"},"knowledgeBaseId":{"type":"string","description":"Knowledge base ID (required for get, query, add_file, list_tags, create_tag, get_tag_usage)"},"name":{"type":"string","description":"Name of the knowledge base (required for 'create')"},"query":{"type":"string","description":"Search query text (required for 'query')"},"sourceConfig":{"type":"object","description":"Connector-specific configuration matching the configFields in knowledgebases/connectors/{type}.json"},"syncIntervalMinutes":{"type":"number","description":"Sync interval in minutes: 60 (hourly), 360 (6h), 1440 (daily), 10080 (weekly), 0 (manual only). Default: 1440","default":1440},"tagDefinitionId":{"type":"string","description":"Tag definition ID (required for update_tag, delete_tag)"},"tagDisplayName":{"type":"string","description":"Display name for the tag (required for create_tag, optional for update_tag)"},"tagFieldType":{"type":"string","description":"Field type: text, number, date, boolean (optional for create_tag, defaults to text)","enum":["text","number","date","boolean"]},"topK":{"type":"number","description":"Number of results to return (1-50, default: 5)","default":5},"workspaceId":{"type":"string","description":"Workspace ID (required for 'create', optional filter for 'list')"}}},"operation":{"type":"string","description":"The operation to perform","enum":["create","get","query","add_file","update","delete","delete_document","update_document","list_tags","create_tag","update_tag","delete_tag","get_tag_usage","add_connector","update_connector","delete_connector","sync_connector"]}},"required":["operation","args"]}, - resultSchema: {"type":"object","properties":{"data":{"type":"object","description":"Operation-specific result payload."},"message":{"type":"string","description":"Human-readable outcome summary."},"success":{"type":"boolean","description":"Whether the operation succeeded."}},"required":["success","message"]}, + id: 'knowledge_base', + name: 'knowledge_base', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + args: { + type: 'object', + description: 'Arguments for the operation', + properties: { + apiKey: { + type: 'string', + description: + 'API key for API-key-based connectors (required when connector auth mode is apiKey)', + }, + chunkingConfig: { + type: 'object', + description: "Chunking configuration (optional for 'create')", + properties: { + maxSize: { + type: 'number', + description: 'Maximum chunk size (100-4000, default: 1024)', + default: 1024, + }, + minSize: { + type: 'number', + description: 'Minimum chunk size (1-2000, default: 1)', + default: 1, + }, + overlap: { + type: 'number', + description: 'Overlap between chunks (0-500, default: 200)', + default: 200, + }, + }, + }, + connectorId: { + type: 'string', + description: + 'Connector ID (required for update_connector, delete_connector, sync_connector)', + }, + connectorStatus: { + type: 'string', + description: 'Connector status (optional for update_connector)', + enum: ['active', 'paused'], + }, + connectorType: { + type: 'string', + description: + "Connector type from registry, e.g. 'confluence', 'google_drive', 'notion' (required for add_connector). Read knowledgebases/connectors/{type}.json for the config schema.", + }, + credentialId: { + type: 'string', + description: + 'OAuth credential ID from environment/credentials.json (required for OAuth connectors)', + }, + description: { + type: 'string', + description: "Description of the knowledge base (optional for 'create')", + }, + disabledTagIds: { + type: 'array', + description: + 'Tag definition IDs to opt out of (optional for add_connector). See tagDefinitions in the connector schema.', + }, + documentId: { + type: 'string', + description: 'Document ID (required for delete_document, update_document)', + }, + enabled: { + type: 'boolean', + description: 'Enable/disable a document (optional for update_document)', + }, + fileId: { + type: 'string', + description: + 'Canonical workspace file ID to add as a document (preferred for add_file). Discover via read("files/{name}/meta.json") or glob("files/by-id/*/meta.json").', + }, + filePath: { + type: 'string', + description: 'Legacy workspace file reference for add_file. Prefer fileId.', + }, + filename: { + type: 'string', + description: 'New filename for a document (optional for update_document)', + }, + knowledgeBaseId: { + type: 'string', + description: + 'Knowledge base ID (required for get, query, add_file, list_tags, create_tag, get_tag_usage)', + }, + name: { + type: 'string', + description: "Name of the knowledge base (required for 'create')", + }, + query: { type: 'string', description: "Search query text (required for 'query')" }, + sourceConfig: { + type: 'object', + description: + 'Connector-specific configuration matching the configFields in knowledgebases/connectors/{type}.json', + }, + syncIntervalMinutes: { + type: 'number', + description: + 'Sync interval in minutes: 60 (hourly), 360 (6h), 1440 (daily), 10080 (weekly), 0 (manual only). Default: 1440', + default: 1440, + }, + tagDefinitionId: { + type: 'string', + description: 'Tag definition ID (required for update_tag, delete_tag)', + }, + tagDisplayName: { + type: 'string', + description: + 'Display name for the tag (required for create_tag, optional for update_tag)', + }, + tagFieldType: { + type: 'string', + description: + 'Field type: text, number, date, boolean (optional for create_tag, defaults to text)', + enum: ['text', 'number', 'date', 'boolean'], + }, + topK: { + type: 'number', + description: 'Number of results to return (1-50, default: 5)', + default: 5, + }, + workspaceId: { + type: 'string', + description: "Workspace ID (required for 'create', optional filter for 'list')", + }, + }, + }, + operation: { + type: 'string', + description: 'The operation to perform', + enum: [ + 'create', + 'get', + 'query', + 'add_file', + 'update', + 'delete', + 'delete_document', + 'update_document', + 'list_tags', + 'create_tag', + 'update_tag', + 'delete_tag', + 'get_tag_usage', + 'add_connector', + 'update_connector', + 'delete_connector', + 'sync_connector', + ], + }, + }, + required: ['operation', 'args'], + }, + resultSchema: { + type: 'object', + properties: { + data: { type: 'object', description: 'Operation-specific result payload.' }, + message: { type: 'string', description: 'Human-readable outcome summary.' }, + success: { type: 'boolean', description: 'Whether the operation succeeded.' }, + }, + required: ['success', 'message'], + }, requiresConfirmation: true, -}; +} export const ListFolders: ToolCatalogEntry = { - id: "list_folders", - name: "list_folders", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"workspaceId":{"type":"string","description":"Optional workspace ID to list folders for."}}}, -}; + id: 'list_folders', + name: 'list_folders', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + workspaceId: { type: 'string', description: 'Optional workspace ID to list folders for.' }, + }, + }, +} export const ListUserWorkspaces: ToolCatalogEntry = { - id: "list_user_workspaces", - name: "list_user_workspaces", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{}}, -}; + id: 'list_user_workspaces', + name: 'list_user_workspaces', + executor: 'sim', + mode: 'async', + parameters: { type: 'object', properties: {} }, +} export const ListWorkspaceMcpServers: ToolCatalogEntry = { - id: "list_workspace_mcp_servers", - name: "list_workspace_mcp_servers", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"workspaceId":{"type":"string","description":"Workspace ID (defaults to current workspace)"}}}, -}; + id: 'list_workspace_mcp_servers', + name: 'list_workspace_mcp_servers', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + workspaceId: { type: 'string', description: 'Workspace ID (defaults to current workspace)' }, + }, + }, +} export const ManageCredential: ToolCatalogEntry = { - id: "manage_credential", - name: "manage_credential", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"credentialId":{"type":"string","description":"The credential ID (from environment/credentials.json)"},"displayName":{"type":"string","description":"New display name (required for rename)"},"operation":{"type":"string","description":"The operation to perform","enum":["rename","delete"]}},"required":["operation","credentialId"]}, + id: 'manage_credential', + name: 'manage_credential', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + credentialId: { + type: 'string', + description: 'The credential ID (from environment/credentials.json)', + }, + displayName: { type: 'string', description: 'New display name (required for rename)' }, + operation: { + type: 'string', + description: 'The operation to perform', + enum: ['rename', 'delete'], + }, + }, + required: ['operation', 'credentialId'], + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const ManageCustomTool: ToolCatalogEntry = { - id: "manage_custom_tool", - name: "manage_custom_tool", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"code":{"type":"string","description":"The JavaScript code that executes when the tool is called (required for add). Parameters from schema are available as variables. Function body only - no signature or wrapping braces."},"operation":{"type":"string","description":"The operation to perform: 'add', 'edit', 'list', or 'delete'","enum":["add","edit","delete","list"]},"schema":{"type":"object","description":"The tool schema in OpenAI function calling format (required for add).","properties":{"function":{"type":"object","description":"The function definition","properties":{"description":{"type":"string","description":"What the function does"},"name":{"type":"string","description":"The function name (camelCase)"},"parameters":{"type":"object","description":"The function parameters schema","properties":{"properties":{"type":"object","description":"Parameter definitions as key-value pairs"},"required":{"type":"array","description":"Array of required parameter names","items":{"type":"string"}},"type":{"type":"string","description":"Must be 'object'"}},"required":["type","properties"]}},"required":["name","parameters"]},"type":{"type":"string","description":"Must be 'function'"}},"required":["type","function"]},"toolId":{"type":"string","description":"The ID of the custom tool (required for edit/delete). Must be the exact toolId from the get_workflow_data custom tool response - do not guess or construct it. DO NOT PROVIDE THE TOOL ID IF THE OPERATION IS 'ADD'."}},"required":["operation"]}, + id: 'manage_custom_tool', + name: 'manage_custom_tool', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + code: { + type: 'string', + description: + 'The JavaScript code that executes when the tool is called (required for add). Parameters from schema are available as variables. Function body only - no signature or wrapping braces.', + }, + operation: { + type: 'string', + description: "The operation to perform: 'add', 'edit', 'list', or 'delete'", + enum: ['add', 'edit', 'delete', 'list'], + }, + schema: { + type: 'object', + description: 'The tool schema in OpenAI function calling format (required for add).', + properties: { + function: { + type: 'object', + description: 'The function definition', + properties: { + description: { type: 'string', description: 'What the function does' }, + name: { type: 'string', description: 'The function name (camelCase)' }, + parameters: { + type: 'object', + description: 'The function parameters schema', + properties: { + properties: { + type: 'object', + description: 'Parameter definitions as key-value pairs', + }, + required: { + type: 'array', + description: 'Array of required parameter names', + items: { type: 'string' }, + }, + type: { type: 'string', description: "Must be 'object'" }, + }, + required: ['type', 'properties'], + }, + }, + required: ['name', 'parameters'], + }, + type: { type: 'string', description: "Must be 'function'" }, + }, + required: ['type', 'function'], + }, + toolId: { + type: 'string', + description: + "The ID of the custom tool (required for edit/delete). Must be the exact toolId from the get_workflow_data custom tool response - do not guess or construct it. DO NOT PROVIDE THE TOOL ID IF THE OPERATION IS 'ADD'.", + }, + }, + required: ['operation'], + }, requiresConfirmation: true, -}; +} export const ManageJob: ToolCatalogEntry = { - id: "manage_job", - name: "manage_job", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"args":{"type":"object","description":"Operation-specific arguments. For create: {title, prompt, cron?, time?, timezone?, lifecycle?, successCondition?, maxRuns?}. For get/delete: {jobId}. For update: {jobId, title?, prompt?, cron?, timezone?, status?, lifecycle?, successCondition?, maxRuns?}. For list: no args needed.","properties":{"cron":{"type":"string","description":"Cron expression for recurring jobs"},"jobId":{"type":"string","description":"Job ID (required for get, update, delete)"},"lifecycle":{"type":"string","description":"'persistent' (default) or 'until_complete'. Until_complete jobs stop when complete_job is called."},"maxRuns":{"type":"integer","description":"Max executions before auto-completing. Safety limit."},"prompt":{"type":"string","description":"The prompt to execute when the job fires"},"status":{"type":"string","description":"Job status: active, paused"},"successCondition":{"type":"string","description":"What must happen for the job to be considered complete (until_complete lifecycle)."},"time":{"type":"string","description":"ISO 8601 datetime for one-time jobs or cron start time"},"timezone":{"type":"string","description":"IANA timezone (e.g. America/New_York). Defaults to UTC."},"title":{"type":"string","description":"Short descriptive title for the job (e.g. 'Email Poller')"}}},"operation":{"type":"string","description":"The operation to perform: create, list, get, update, delete","enum":["create","list","get","update","delete"]}},"required":["operation"]}, -}; + id: 'manage_job', + name: 'manage_job', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + args: { + type: 'object', + description: + 'Operation-specific arguments. For create: {title, prompt, cron?, time?, timezone?, lifecycle?, successCondition?, maxRuns?}. For get/delete: {jobId}. For update: {jobId, title?, prompt?, cron?, timezone?, status?, lifecycle?, successCondition?, maxRuns?}. For list: no args needed.', + properties: { + cron: { type: 'string', description: 'Cron expression for recurring jobs' }, + jobId: { type: 'string', description: 'Job ID (required for get, update, delete)' }, + lifecycle: { + type: 'string', + description: + "'persistent' (default) or 'until_complete'. Until_complete jobs stop when complete_job is called.", + }, + maxRuns: { + type: 'integer', + description: 'Max executions before auto-completing. Safety limit.', + }, + prompt: { type: 'string', description: 'The prompt to execute when the job fires' }, + status: { type: 'string', description: 'Job status: active, paused' }, + successCondition: { + type: 'string', + description: + 'What must happen for the job to be considered complete (until_complete lifecycle).', + }, + time: { + type: 'string', + description: 'ISO 8601 datetime for one-time jobs or cron start time', + }, + timezone: { + type: 'string', + description: 'IANA timezone (e.g. America/New_York). Defaults to UTC.', + }, + title: { + type: 'string', + description: "Short descriptive title for the job (e.g. 'Email Poller')", + }, + }, + }, + operation: { + type: 'string', + description: 'The operation to perform: create, list, get, update, delete', + enum: ['create', 'list', 'get', 'update', 'delete'], + }, + }, + required: ['operation'], + }, +} export const ManageMcpTool: ToolCatalogEntry = { - id: "manage_mcp_tool", - name: "manage_mcp_tool", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"config":{"type":"object","description":"Required for add and edit. The MCP server configuration.","properties":{"enabled":{"type":"boolean","description":"Whether the server is enabled (default: true)"},"headers":{"type":"object","description":"Optional HTTP headers to send with requests (key-value pairs)"},"name":{"type":"string","description":"Display name for the MCP server"},"timeout":{"type":"number","description":"Request timeout in milliseconds (default: 30000)"},"transport":{"type":"string","description":"Transport protocol: 'streamable-http' or 'sse'","enum":["streamable-http","sse"],"default":"streamable-http"},"url":{"type":"string","description":"The MCP server endpoint URL (required for add)"}}},"operation":{"type":"string","description":"The operation to perform: 'add', 'edit', 'list', or 'delete'","enum":["add","edit","delete","list"]},"serverId":{"type":"string","description":"Required for edit and delete. The database ID of the MCP server. DO NOT PROVIDE if operation is 'add' or 'list'."}},"required":["operation"]}, + id: 'manage_mcp_tool', + name: 'manage_mcp_tool', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + config: { + type: 'object', + description: 'Required for add and edit. The MCP server configuration.', + properties: { + enabled: { + type: 'boolean', + description: 'Whether the server is enabled (default: true)', + }, + headers: { + type: 'object', + description: 'Optional HTTP headers to send with requests (key-value pairs)', + }, + name: { type: 'string', description: 'Display name for the MCP server' }, + timeout: { + type: 'number', + description: 'Request timeout in milliseconds (default: 30000)', + }, + transport: { + type: 'string', + description: "Transport protocol: 'streamable-http' or 'sse'", + enum: ['streamable-http', 'sse'], + default: 'streamable-http', + }, + url: { type: 'string', description: 'The MCP server endpoint URL (required for add)' }, + }, + }, + operation: { + type: 'string', + description: "The operation to perform: 'add', 'edit', 'list', or 'delete'", + enum: ['add', 'edit', 'delete', 'list'], + }, + serverId: { + type: 'string', + description: + "Required for edit and delete. The database ID of the MCP server. DO NOT PROVIDE if operation is 'add' or 'list'.", + }, + }, + required: ['operation'], + }, requiresConfirmation: true, - requiredPermission: "write", -}; + requiredPermission: 'write', +} export const ManageSkill: ToolCatalogEntry = { - id: "manage_skill", - name: "manage_skill", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"content":{"type":"string","description":"Markdown instructions for the skill. Required for add, optional for edit."},"description":{"type":"string","description":"Short description of the skill. Required for add, optional for edit."},"name":{"type":"string","description":"Skill name in kebab-case (e.g. 'my-skill'). Required for add, optional for edit."},"operation":{"type":"string","description":"The operation to perform: 'add', 'edit', 'list', or 'delete'","enum":["add","edit","delete","list"]},"skillId":{"type":"string","description":"The ID of the skill (required for edit/delete). Must be the exact ID from the VFS or list. DO NOT PROVIDE if operation is 'add' or 'list'."}},"required":["operation"]}, + id: 'manage_skill', + name: 'manage_skill', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + content: { + type: 'string', + description: 'Markdown instructions for the skill. Required for add, optional for edit.', + }, + description: { + type: 'string', + description: 'Short description of the skill. Required for add, optional for edit.', + }, + name: { + type: 'string', + description: + "Skill name in kebab-case (e.g. 'my-skill'). Required for add, optional for edit.", + }, + operation: { + type: 'string', + description: "The operation to perform: 'add', 'edit', 'list', or 'delete'", + enum: ['add', 'edit', 'delete', 'list'], + }, + skillId: { + type: 'string', + description: + "The ID of the skill (required for edit/delete). Must be the exact ID from the VFS or list. DO NOT PROVIDE if operation is 'add' or 'list'.", + }, + }, + required: ['operation'], + }, requiresConfirmation: true, - requiredPermission: "write", -}; + requiredPermission: 'write', +} export const MaterializeFile: ToolCatalogEntry = { - id: "materialize_file", - name: "materialize_file", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"fileName":{"type":"string","description":"The name of the uploaded file to materialize (e.g. \"report.pdf\")"},"knowledgeBaseId":{"type":"string","description":"ID of an existing knowledge base to add the file to (only used with operation \"knowledge_base\"). If omitted, a new KB is created."},"operation":{"type":"string","description":"What to do with the file. \"save\" promotes it to files/. \"import\" imports a workflow JSON. \"table\" converts CSV/TSV/JSON to a table. \"knowledge_base\" saves and adds to a KB. Defaults to \"save\".","enum":["save","import","table","knowledge_base"],"default":"save"},"tableName":{"type":"string","description":"Custom name for the table (only used with operation \"table\"). Defaults to the file name without extension."}},"required":["fileName"]}, - requiredPermission: "write", -}; + id: 'materialize_file', + name: 'materialize_file', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + fileName: { + type: 'string', + description: 'The name of the uploaded file to materialize (e.g. "report.pdf")', + }, + knowledgeBaseId: { + type: 'string', + description: + 'ID of an existing knowledge base to add the file to (only used with operation "knowledge_base"). If omitted, a new KB is created.', + }, + operation: { + type: 'string', + description: + 'What to do with the file. "save" promotes it to files/. "import" imports a workflow JSON. "table" converts CSV/TSV/JSON to a table. "knowledge_base" saves and adds to a KB. Defaults to "save".', + enum: ['save', 'import', 'table', 'knowledge_base'], + default: 'save', + }, + tableName: { + type: 'string', + description: + 'Custom name for the table (only used with operation "table"). Defaults to the file name without extension.', + }, + }, + required: ['fileName'], + }, + requiredPermission: 'write', +} export const MoveFolder: ToolCatalogEntry = { - id: "move_folder", - name: "move_folder", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"folderId":{"type":"string","description":"The folder ID to move."},"parentId":{"type":"string","description":"Target parent folder ID. Omit or pass empty string to move to workspace root."}},"required":["folderId"]}, - requiredPermission: "write", -}; + id: 'move_folder', + name: 'move_folder', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + folderId: { type: 'string', description: 'The folder ID to move.' }, + parentId: { + type: 'string', + description: + 'Target parent folder ID. Omit or pass empty string to move to workspace root.', + }, + }, + required: ['folderId'], + }, + requiredPermission: 'write', +} export const MoveWorkflow: ToolCatalogEntry = { - id: "move_workflow", - name: "move_workflow", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"folderId":{"type":"string","description":"Target folder ID. Omit or pass empty string to move to workspace root."},"workflowId":{"type":"string","description":"The workflow ID to move."}},"required":["workflowId"]}, - requiredPermission: "write", -}; + id: 'move_workflow', + name: 'move_workflow', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + folderId: { + type: 'string', + description: 'Target folder ID. Omit or pass empty string to move to workspace root.', + }, + workflowId: { type: 'string', description: 'The workflow ID to move.' }, + }, + required: ['workflowId'], + }, + requiredPermission: 'write', +} export const OauthGetAuthLink: ToolCatalogEntry = { - id: "oauth_get_auth_link", - name: "oauth_get_auth_link", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"providerName":{"type":"string","description":"The name of the OAuth provider to connect (e.g., 'Slack', 'Gmail', 'Google Calendar', 'GitHub')"}},"required":["providerName"]}, -}; + id: 'oauth_get_auth_link', + name: 'oauth_get_auth_link', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + providerName: { + type: 'string', + description: + "The name of the OAuth provider to connect (e.g., 'Slack', 'Gmail', 'Google Calendar', 'GitHub')", + }, + }, + required: ['providerName'], + }, +} export const OauthRequestAccess: ToolCatalogEntry = { - id: "oauth_request_access", - name: "oauth_request_access", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"providerName":{"type":"string","description":"The name of the OAuth provider to connect (e.g., 'Slack', 'Gmail', 'Google Calendar')"}},"required":["providerName"]}, + id: 'oauth_request_access', + name: 'oauth_request_access', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + providerName: { + type: 'string', + description: + "The name of the OAuth provider to connect (e.g., 'Slack', 'Gmail', 'Google Calendar')", + }, + }, + required: ['providerName'], + }, requiresConfirmation: true, -}; +} export const OpenResource: ToolCatalogEntry = { - id: "open_resource", - name: "open_resource", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"id":{"type":"string","description":"The resource ID to open."},"type":{"type":"string","description":"The resource type to open.","enum":["workflow","table","knowledgebase","file"]}},"required":["type","id"]}, -}; + id: 'open_resource', + name: 'open_resource', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + id: { type: 'string', description: 'The resource ID to open.' }, + type: { + type: 'string', + description: 'The resource type to open.', + enum: ['workflow', 'table', 'knowledgebase', 'file'], + }, + }, + required: ['type', 'id'], + }, +} export const Read: ToolCatalogEntry = { - id: "read", - name: "read", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"limit":{"type":"number","description":"Maximum number of lines to read."},"offset":{"type":"number","description":"Line offset to start reading from (0-indexed)."},"outputTable":{"type":"string","description":"Table ID to import the file contents into (CSV/JSON). All existing rows are replaced. Example: \"tbl_abc123\""},"path":{"type":"string","description":"Path to the file to read (e.g. 'workflows/My Workflow/state.json')."}},"required":["path"]}, -}; + id: 'read', + name: 'read', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + limit: { type: 'number', description: 'Maximum number of lines to read.' }, + offset: { type: 'number', description: 'Line offset to start reading from (0-indexed).' }, + outputTable: { + type: 'string', + description: + 'Table ID to import the file contents into (CSV/JSON). All existing rows are replaced. Example: "tbl_abc123"', + }, + path: { + type: 'string', + description: "Path to the file to read (e.g. 'workflows/My Workflow/state.json').", + }, + }, + required: ['path'], + }, +} export const Redeploy: ToolCatalogEntry = { - id: "redeploy", - name: "redeploy", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"workflowId":{"type":"string","description":"Workflow ID to redeploy (required in workspace context)"}}}, + id: 'redeploy', + name: 'redeploy', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + workflowId: { + type: 'string', + description: 'Workflow ID to redeploy (required in workspace context)', + }, + }, + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const RenameFile: ToolCatalogEntry = { - id: "rename_file", - name: "rename_file", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"fileId":{"type":"string","description":"Canonical workspace file ID of the file to rename."},"newName":{"type":"string","description":"New filename including extension, e.g. \"draft_v2.md\". Must not contain slashes."}},"required":["fileId","newName"]}, - resultSchema: {"type":"object","properties":{"data":{"type":"object","description":"Contains id and the new name."},"message":{"type":"string","description":"Human-readable outcome."},"success":{"type":"boolean","description":"Whether the rename succeeded."}},"required":["success","message"]}, - requiredPermission: "write", -}; + id: 'rename_file', + name: 'rename_file', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + fileId: { type: 'string', description: 'Canonical workspace file ID of the file to rename.' }, + newName: { + type: 'string', + description: + 'New filename including extension, e.g. "draft_v2.md". Must not contain slashes.', + }, + }, + required: ['fileId', 'newName'], + }, + resultSchema: { + type: 'object', + properties: { + data: { type: 'object', description: 'Contains id and the new name.' }, + message: { type: 'string', description: 'Human-readable outcome.' }, + success: { type: 'boolean', description: 'Whether the rename succeeded.' }, + }, + required: ['success', 'message'], + }, + requiredPermission: 'write', +} export const RenameWorkflow: ToolCatalogEntry = { - id: "rename_workflow", - name: "rename_workflow", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"name":{"type":"string","description":"The new name for the workflow."},"workflowId":{"type":"string","description":"The workflow ID to rename."}},"required":["workflowId","name"]}, - requiredPermission: "write", -}; + id: 'rename_workflow', + name: 'rename_workflow', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + name: { type: 'string', description: 'The new name for the workflow.' }, + workflowId: { type: 'string', description: 'The workflow ID to rename.' }, + }, + required: ['workflowId', 'name'], + }, + requiredPermission: 'write', +} export const Research: ToolCatalogEntry = { - id: "research", - name: "research", - executor: "subagent", - mode: "async", - parameters: {"properties":{"topic":{"description":"The topic to research.","type":"string"}},"required":["topic"],"type":"object"}, - subagentId: "research", + id: 'research', + name: 'research', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { topic: { description: 'The topic to research.', type: 'string' } }, + required: ['topic'], + type: 'object', + }, + subagentId: 'research', internal: true, -}; +} export const Respond: ToolCatalogEntry = { - id: "respond", - name: "respond", - executor: "sim", - mode: "async", - parameters: {"additionalProperties":true,"properties":{"output":{"description":"The result — facts, status, VFS paths to persisted data, whatever the caller needs to act on.","type":"string"},"success":{"description":"Whether the task completed successfully","type":"boolean"},"type":{"description":"Optional logical result type override","type":"string"}},"required":["output","success"],"type":"object"}, + id: 'respond', + name: 'respond', + executor: 'sim', + mode: 'async', + parameters: { + additionalProperties: true, + properties: { + output: { + description: + 'The result — facts, status, VFS paths to persisted data, whatever the caller needs to act on.', + type: 'string', + }, + success: { description: 'Whether the task completed successfully', type: 'boolean' }, + type: { description: 'Optional logical result type override', type: 'string' }, + }, + required: ['output', 'success'], + type: 'object', + }, internal: true, hidden: true, -}; +} export const RevertToVersion: ToolCatalogEntry = { - id: "revert_to_version", - name: "revert_to_version", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"version":{"type":"number","description":"The deployment version number to revert to"},"workflowId":{"type":"string","description":"The workflow ID"}},"required":["workflowId","version"]}, + id: 'revert_to_version', + name: 'revert_to_version', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + version: { type: 'number', description: 'The deployment version number to revert to' }, + workflowId: { type: 'string', description: 'The workflow ID' }, + }, + required: ['workflowId', 'version'], + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const Run: ToolCatalogEntry = { - id: "run", - name: "run", - executor: "subagent", - mode: "async", - parameters: {"properties":{"context":{"description":"Pre-gathered context: workflow state, block IDs, input requirements.","type":"string"},"request":{"description":"What to run or what logs to check.","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "run", + id: 'run', + name: 'run', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { + context: { + description: 'Pre-gathered context: workflow state, block IDs, input requirements.', + type: 'string', + }, + request: { description: 'What to run or what logs to check.', type: 'string' }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'run', internal: true, -}; +} export const RunBlock: ToolCatalogEntry = { - id: "run_block", - name: "run_block", - executor: "client", - mode: "async", - parameters: {"type":"object","properties":{"blockId":{"type":"string","description":"The block ID to run in isolation."},"executionId":{"type":"string","description":"Optional execution ID to load the snapshot from. Uses latest execution if omitted."},"useDeployedState":{"type":"boolean","description":"When true, runs the deployed version instead of the live draft. Default: false (draft)."},"workflowId":{"type":"string","description":"Optional workflow ID to run. If not provided, uses the current workflow in context."},"workflow_input":{"type":"object","description":"JSON object with key-value mappings where each key is an input field name"}},"required":["blockId"]}, + id: 'run_block', + name: 'run_block', + executor: 'client', + mode: 'async', + parameters: { + type: 'object', + properties: { + blockId: { type: 'string', description: 'The block ID to run in isolation.' }, + executionId: { + type: 'string', + description: + 'Optional execution ID to load the snapshot from. Uses latest execution if omitted.', + }, + useDeployedState: { + type: 'boolean', + description: + 'When true, runs the deployed version instead of the live draft. Default: false (draft).', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to run. If not provided, uses the current workflow in context.', + }, + workflow_input: { + type: 'object', + description: 'JSON object with key-value mappings where each key is an input field name', + }, + }, + required: ['blockId'], + }, clientExecutable: true, requiresConfirmation: true, -}; +} export const RunFromBlock: ToolCatalogEntry = { - id: "run_from_block", - name: "run_from_block", - executor: "client", - mode: "async", - parameters: {"type":"object","properties":{"executionId":{"type":"string","description":"Optional execution ID to load the snapshot from. Uses latest execution if omitted."},"startBlockId":{"type":"string","description":"The block ID to start execution from."},"useDeployedState":{"type":"boolean","description":"When true, runs the deployed version instead of the live draft. Default: false (draft)."},"workflowId":{"type":"string","description":"Optional workflow ID to run. If not provided, uses the current workflow in context."},"workflow_input":{"type":"object","description":"JSON object with key-value mappings where each key is an input field name"}},"required":["startBlockId"]}, + id: 'run_from_block', + name: 'run_from_block', + executor: 'client', + mode: 'async', + parameters: { + type: 'object', + properties: { + executionId: { + type: 'string', + description: + 'Optional execution ID to load the snapshot from. Uses latest execution if omitted.', + }, + startBlockId: { type: 'string', description: 'The block ID to start execution from.' }, + useDeployedState: { + type: 'boolean', + description: + 'When true, runs the deployed version instead of the live draft. Default: false (draft).', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to run. If not provided, uses the current workflow in context.', + }, + workflow_input: { + type: 'object', + description: 'JSON object with key-value mappings where each key is an input field name', + }, + }, + required: ['startBlockId'], + }, clientExecutable: true, requiresConfirmation: true, -}; +} export const RunWorkflow: ToolCatalogEntry = { - id: "run_workflow", - name: "run_workflow", - executor: "client", - mode: "async", - parameters: {"type":"object","properties":{"useDeployedState":{"type":"boolean","description":"When true, runs the deployed version instead of the live draft. Default: false (draft)."},"workflowId":{"type":"string","description":"Optional workflow ID to run. If not provided, uses the current workflow in context."},"workflow_input":{"type":"object","description":"JSON object with key-value mappings where each key is an input field name"}},"required":["workflow_input"]}, + id: 'run_workflow', + name: 'run_workflow', + executor: 'client', + mode: 'async', + parameters: { + type: 'object', + properties: { + useDeployedState: { + type: 'boolean', + description: + 'When true, runs the deployed version instead of the live draft. Default: false (draft).', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to run. If not provided, uses the current workflow in context.', + }, + workflow_input: { + type: 'object', + description: 'JSON object with key-value mappings where each key is an input field name', + }, + }, + required: ['workflow_input'], + }, clientExecutable: true, requiresConfirmation: true, -}; +} export const RunWorkflowUntilBlock: ToolCatalogEntry = { - id: "run_workflow_until_block", - name: "run_workflow_until_block", - executor: "client", - mode: "async", - parameters: {"type":"object","properties":{"stopAfterBlockId":{"type":"string","description":"The block ID to stop after. Execution halts once this block completes."},"useDeployedState":{"type":"boolean","description":"When true, runs the deployed version instead of the live draft. Default: false (draft)."},"workflowId":{"type":"string","description":"Optional workflow ID to run. If not provided, uses the current workflow in context."},"workflow_input":{"type":"object","description":"JSON object with key-value mappings where each key is an input field name"}},"required":["stopAfterBlockId"]}, + id: 'run_workflow_until_block', + name: 'run_workflow_until_block', + executor: 'client', + mode: 'async', + parameters: { + type: 'object', + properties: { + stopAfterBlockId: { + type: 'string', + description: 'The block ID to stop after. Execution halts once this block completes.', + }, + useDeployedState: { + type: 'boolean', + description: + 'When true, runs the deployed version instead of the live draft. Default: false (draft).', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to run. If not provided, uses the current workflow in context.', + }, + workflow_input: { + type: 'object', + description: 'JSON object with key-value mappings where each key is an input field name', + }, + }, + required: ['stopAfterBlockId'], + }, clientExecutable: true, requiresConfirmation: true, -}; +} export const ScrapePage: ToolCatalogEntry = { - id: "scrape_page", - name: "scrape_page", - executor: "go", - mode: "sync", - parameters: {"type":"object","properties":{"include_links":{"type":"boolean","description":"Extract all links from the page (default false)"},"url":{"type":"string","description":"The URL to scrape (must include https://)"},"wait_for":{"type":"string","description":"CSS selector to wait for before scraping (for JS-heavy pages)"}},"required":["url"]}, -}; + id: 'scrape_page', + name: 'scrape_page', + executor: 'go', + mode: 'sync', + parameters: { + type: 'object', + properties: { + include_links: { + type: 'boolean', + description: 'Extract all links from the page (default false)', + }, + url: { type: 'string', description: 'The URL to scrape (must include https://)' }, + wait_for: { + type: 'string', + description: 'CSS selector to wait for before scraping (for JS-heavy pages)', + }, + }, + required: ['url'], + }, +} export const SearchDocumentation: ToolCatalogEntry = { - id: "search_documentation", - name: "search_documentation", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"query":{"type":"string","description":"The search query"},"topK":{"type":"number","description":"Number of results (max 10)"}},"required":["query"]}, -}; + id: 'search_documentation', + name: 'search_documentation', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + query: { type: 'string', description: 'The search query' }, + topK: { type: 'number', description: 'Number of results (max 10)' }, + }, + required: ['query'], + }, +} export const SearchLibraryDocs: ToolCatalogEntry = { - id: "search_library_docs", - name: "search_library_docs", - executor: "go", - mode: "sync", - parameters: {"type":"object","properties":{"library_name":{"type":"string","description":"Name of the library to search for (e.g., 'nextjs', 'stripe', 'langchain')"},"query":{"type":"string","description":"The question or topic to find documentation for - be specific"},"version":{"type":"string","description":"Specific version (optional, e.g., '14', 'v2')"}},"required":["library_name","query"]}, -}; + id: 'search_library_docs', + name: 'search_library_docs', + executor: 'go', + mode: 'sync', + parameters: { + type: 'object', + properties: { + library_name: { + type: 'string', + description: "Name of the library to search for (e.g., 'nextjs', 'stripe', 'langchain')", + }, + query: { + type: 'string', + description: 'The question or topic to find documentation for - be specific', + }, + version: { type: 'string', description: "Specific version (optional, e.g., '14', 'v2')" }, + }, + required: ['library_name', 'query'], + }, +} export const SearchOnline: ToolCatalogEntry = { - id: "search_online", - name: "search_online", - executor: "go", - mode: "sync", - parameters: {"type":"object","properties":{"category":{"type":"string","description":"Filter by category","enum":["news","tweet","github","paper","company","research paper","linkedin profile","pdf","personal site"]},"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"}},"required":["query"]}, -}; + id: 'search_online', + name: 'search_online', + executor: 'go', + mode: 'sync', + parameters: { + type: 'object', + properties: { + category: { + type: 'string', + description: 'Filter by category', + enum: [ + 'news', + 'tweet', + 'github', + 'paper', + 'company', + 'research paper', + 'linkedin profile', + 'pdf', + 'personal site', + ], + }, + 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' }, + }, + required: ['query'], + }, +} export const SearchPatterns: ToolCatalogEntry = { - id: "search_patterns", - name: "search_patterns", - executor: "go", - mode: "sync", - parameters: {"type":"object","properties":{"limit":{"type":"integer","description":"Maximum number of unique pattern examples to return (defaults to 3)."},"queries":{"type":"array","description":"Up to 3 descriptive strings explaining the workflow pattern(s) you need. Focus on intent and desired outcomes.","items":{"type":"string","description":"Example: \"how to automate wealthbox meeting notes into follow-up tasks\""}}},"required":["queries"]}, -}; + id: 'search_patterns', + name: 'search_patterns', + executor: 'go', + mode: 'sync', + parameters: { + type: 'object', + properties: { + limit: { + type: 'integer', + description: 'Maximum number of unique pattern examples to return (defaults to 3).', + }, + queries: { + type: 'array', + description: + 'Up to 3 descriptive strings explaining the workflow pattern(s) you need. Focus on intent and desired outcomes.', + items: { + type: 'string', + description: 'Example: "how to automate wealthbox meeting notes into follow-up tasks"', + }, + }, + }, + required: ['queries'], + }, +} export const SetEnvironmentVariables: ToolCatalogEntry = { - id: "set_environment_variables", - name: "set_environment_variables", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"variables":{"type":"array","description":"List of env vars to set","items":{"type":"object","properties":{"name":{"type":"string","description":"Variable name"},"value":{"type":"string","description":"Variable value"}},"required":["name","value"]}}},"required":["variables"]}, + id: 'set_environment_variables', + name: 'set_environment_variables', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + variables: { + type: 'array', + description: 'List of env vars to set', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Variable name' }, + value: { type: 'string', description: 'Variable value' }, + }, + required: ['name', 'value'], + }, + }, + }, + required: ['variables'], + }, requiresConfirmation: true, - requiredPermission: "write", -}; + requiredPermission: 'write', +} export const SetGlobalWorkflowVariables: ToolCatalogEntry = { - id: "set_global_workflow_variables", - name: "set_global_workflow_variables", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"operations":{"type":"array","description":"List of operations to apply","items":{"type":"object","properties":{"name":{"type":"string"},"operation":{"type":"string","enum":["add","delete","edit"]},"type":{"type":"string","enum":["plain","number","boolean","array","object"]},"value":{"type":"string"}},"required":["operation","name","type","value"]}},"workflowId":{"type":"string","description":"Optional workflow ID. If not provided, uses the current workflow in context."}},"required":["operations"]}, + id: 'set_global_workflow_variables', + name: 'set_global_workflow_variables', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + operations: { + type: 'array', + description: 'List of operations to apply', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + operation: { type: 'string', enum: ['add', 'delete', 'edit'] }, + type: { type: 'string', enum: ['plain', 'number', 'boolean', 'array', 'object'] }, + value: { type: 'string' }, + }, + required: ['operation', 'name', 'type', 'value'], + }, + }, + workflowId: { + type: 'string', + description: 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + required: ['operations'], + }, requiresConfirmation: true, - requiredPermission: "write", -}; + requiredPermission: 'write', +} export const Superagent: ToolCatalogEntry = { - id: "superagent", - name: "superagent", - executor: "subagent", - mode: "async", - parameters: {"properties":{"task":{"description":"A single sentence — the agent has full conversation context. Do NOT pre-read credentials or look up configs. Example: 'send the email we discussed' or 'check my calendar for tomorrow'.","type":"string"}},"required":["task"],"type":"object"}, - subagentId: "superagent", + id: 'superagent', + name: 'superagent', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { + task: { + description: + "A single sentence — the agent has full conversation context. Do NOT pre-read credentials or look up configs. Example: 'send the email we discussed' or 'check my calendar for tomorrow'.", + type: 'string', + }, + }, + required: ['task'], + type: 'object', + }, + subagentId: 'superagent', internal: true, -}; +} export const Table: ToolCatalogEntry = { - id: "table", - name: "table", - executor: "subagent", - mode: "async", - parameters: {"properties":{"request":{"description":"What table action is needed.","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "table", + id: 'table', + name: 'table', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { request: { description: 'What table action is needed.', type: 'string' } }, + required: ['request'], + type: 'object', + }, + subagentId: 'table', internal: true, -}; +} export const ToolSearchToolRegex: ToolCatalogEntry = { - id: "tool_search_tool_regex", - name: "tool_search_tool_regex", - executor: "sim", - mode: "async", - parameters: {"properties":{"case_insensitive":{"description":"Whether the regex should be case-insensitive (default true).","type":"boolean"},"max_results":{"description":"Maximum number of tools to return (optional).","type":"integer"},"pattern":{"description":"Regular expression to match tool names or descriptions.","type":"string"}},"required":["pattern"],"type":"object"}, -}; + id: 'tool_search_tool_regex', + name: 'tool_search_tool_regex', + executor: 'sim', + mode: 'async', + parameters: { + properties: { + case_insensitive: { + description: 'Whether the regex should be case-insensitive (default true).', + type: 'boolean', + }, + max_results: { + description: 'Maximum number of tools to return (optional).', + type: 'integer', + }, + pattern: { + description: 'Regular expression to match tool names or descriptions.', + type: 'string', + }, + }, + required: ['pattern'], + type: 'object', + }, +} export const UpdateJobHistory: ToolCatalogEntry = { - id: "update_job_history", - name: "update_job_history", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"jobId":{"type":"string","description":"The job ID."},"summary":{"type":"string","description":"A concise summary of what was done this run (e.g., 'Sent follow-up emails to 3 leads: Alice, Bob, Carol')."}},"required":["jobId","summary"]}, -}; + id: 'update_job_history', + name: 'update_job_history', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + jobId: { type: 'string', description: 'The job ID.' }, + summary: { + type: 'string', + description: + "A concise summary of what was done this run (e.g., 'Sent follow-up emails to 3 leads: Alice, Bob, Carol').", + }, + }, + required: ['jobId', 'summary'], + }, +} export const UpdateWorkspaceMcpServer: ToolCatalogEntry = { - id: "update_workspace_mcp_server", - name: "update_workspace_mcp_server", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"description":{"type":"string","description":"New description for the server"},"isPublic":{"type":"boolean","description":"Whether the server is publicly accessible"},"name":{"type":"string","description":"New name for the server"},"serverId":{"type":"string","description":"Required: the MCP server ID to update"}},"required":["serverId"]}, + id: 'update_workspace_mcp_server', + name: 'update_workspace_mcp_server', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + description: { type: 'string', description: 'New description for the server' }, + isPublic: { type: 'boolean', description: 'Whether the server is publicly accessible' }, + name: { type: 'string', description: 'New name for the server' }, + serverId: { type: 'string', description: 'Required: the MCP server ID to update' }, + }, + required: ['serverId'], + }, requiresConfirmation: true, - requiredPermission: "admin", -}; + requiredPermission: 'admin', +} export const UserMemory: ToolCatalogEntry = { - id: "user_memory", - name: "user_memory", - executor: "go", - mode: "sync", - parameters: {"type":"object","properties":{"confidence":{"type":"number","description":"Confidence level 0-1 (default 1.0 for explicit, 0.8 for inferred)"},"correct_value":{"type":"string","description":"The correct value to replace the wrong one (for 'correct' operation)"},"key":{"type":"string","description":"Unique key for the memory (e.g., 'preferred_model', 'slack_credential')"},"limit":{"type":"number","description":"Number of results for search (default 10)"},"memory_type":{"type":"string","description":"Type of memory: 'preference', 'entity', 'history', or 'correction'","enum":["preference","entity","history","correction"]},"operation":{"type":"string","description":"Operation: 'add', 'search', 'delete', 'correct', or 'list'","enum":["add","search","delete","correct","list"]},"query":{"type":"string","description":"Search query to find relevant memories"},"source":{"type":"string","description":"Source: 'explicit' (user told you) or 'inferred' (you observed)","enum":["explicit","inferred"]},"value":{"type":"string","description":"Value to remember"}},"required":["operation"]}, -}; + id: 'user_memory', + name: 'user_memory', + executor: 'go', + mode: 'sync', + parameters: { + type: 'object', + properties: { + confidence: { + type: 'number', + description: 'Confidence level 0-1 (default 1.0 for explicit, 0.8 for inferred)', + }, + correct_value: { + type: 'string', + description: "The correct value to replace the wrong one (for 'correct' operation)", + }, + key: { + type: 'string', + description: "Unique key for the memory (e.g., 'preferred_model', 'slack_credential')", + }, + limit: { type: 'number', description: 'Number of results for search (default 10)' }, + memory_type: { + type: 'string', + description: "Type of memory: 'preference', 'entity', 'history', or 'correction'", + enum: ['preference', 'entity', 'history', 'correction'], + }, + operation: { + type: 'string', + description: "Operation: 'add', 'search', 'delete', 'correct', or 'list'", + enum: ['add', 'search', 'delete', 'correct', 'list'], + }, + query: { type: 'string', description: 'Search query to find relevant memories' }, + source: { + type: 'string', + description: "Source: 'explicit' (user told you) or 'inferred' (you observed)", + enum: ['explicit', 'inferred'], + }, + value: { type: 'string', description: 'Value to remember' }, + }, + required: ['operation'], + }, +} export const UserTable: ToolCatalogEntry = { - id: "user_table", - name: "user_table", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"args":{"type":"object","description":"Arguments for the operation","properties":{"column":{"type":"object","description":"Column definition for add_column: { name, type, unique?, position? }"},"columnName":{"type":"string","description":"Column name (required for rename_column, update_column; use columnNames array for batch delete_column)"},"columnNames":{"type":"array","description":"Array of column names to delete at once (for delete_column). Preferred over columnName when deleting multiple columns."},"data":{"type":"object","description":"Row data as key-value pairs (required for insert_row, update_row)"},"description":{"type":"string","description":"Table description (optional for 'create')"},"fileId":{"type":"string","description":"Canonical workspace file ID for create_from_file/import_file. Discover via read(\"files/{name}/meta.json\") or glob(\"files/by-id/*/meta.json\")."},"filePath":{"type":"string","description":"Legacy workspace file reference for create_from_file/import_file. Prefer fileId."},"filter":{"type":"object","description":"MongoDB-style filter for query_rows, update_rows_by_filter, delete_rows_by_filter"},"limit":{"type":"number","description":"Maximum rows to return or affect (optional, default 100)"},"name":{"type":"string","description":"Table name (required for 'create')"},"newName":{"type":"string","description":"New column name (required for rename_column)"},"newType":{"type":"string","description":"New column type (optional for update_column). Types: string, number, boolean, date, json"},"offset":{"type":"number","description":"Number of rows to skip (optional for query_rows, default 0)"},"outputFormat":{"type":"string","description":"Explicit format override for outputPath. Usually unnecessary — the file extension determines the format automatically. Only use this to force a different format than what the extension implies.","enum":["json","csv","txt","md","html"]},"outputPath":{"type":"string","description":"Pipe query_rows results directly to a NEW workspace file. The format is auto-inferred from the file extension: .csv → CSV, .json → JSON, .md → Markdown, etc. Use .csv for tabular exports. Use a flat path like \"files/export.csv\" — nested paths are not supported."},"rowId":{"type":"string","description":"Row ID (required for get_row, update_row, delete_row)"},"rowIds":{"type":"array","description":"Array of row IDs to delete (for batch_delete_rows)"},"rows":{"type":"array","description":"Array of row data objects (required for batch_insert_rows)"},"schema":{"type":"object","description":"Table schema with columns array (required for 'create'). Each column: { name, type, unique? }"},"sort":{"type":"object","description":"Sort specification as { field: 'asc' | 'desc' } (optional for query_rows)"},"tableId":{"type":"string","description":"Table ID (required for most operations except 'create')"},"unique":{"type":"boolean","description":"Set column unique constraint (optional for update_column)"},"updates":{"type":"array","description":"Array of per-row updates: [{ rowId, data: { col: val } }] (for batch_update_rows)"},"values":{"type":"object","description":"Map of rowId to value for single-column batch update: { \"rowId1\": val1, \"rowId2\": val2 } (for batch_update_rows with columnName)"}}},"operation":{"type":"string","description":"The operation to perform","enum":["create","create_from_file","import_file","get","get_schema","delete","insert_row","batch_insert_rows","get_row","query_rows","update_row","delete_row","update_rows_by_filter","delete_rows_by_filter","batch_update_rows","batch_delete_rows","add_column","rename_column","delete_column","update_column"]}},"required":["operation","args"]}, - resultSchema: {"type":"object","properties":{"data":{"type":"object","description":"Operation-specific result payload."},"message":{"type":"string","description":"Human-readable outcome summary."},"success":{"type":"boolean","description":"Whether the operation succeeded."}},"required":["success","message"]}, + id: 'user_table', + name: 'user_table', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + args: { + type: 'object', + description: 'Arguments for the operation', + properties: { + column: { + type: 'object', + description: 'Column definition for add_column: { name, type, unique?, position? }', + }, + columnName: { + type: 'string', + description: + 'Column name (required for rename_column, update_column; use columnNames array for batch delete_column)', + }, + columnNames: { + type: 'array', + description: + 'Array of column names to delete at once (for delete_column). Preferred over columnName when deleting multiple columns.', + }, + data: { + type: 'object', + description: 'Row data as key-value pairs (required for insert_row, update_row)', + }, + description: { type: 'string', description: "Table description (optional for 'create')" }, + fileId: { + type: 'string', + description: + 'Canonical workspace file ID for create_from_file/import_file. Discover via read("files/{name}/meta.json") or glob("files/by-id/*/meta.json").', + }, + filePath: { + type: 'string', + description: + 'Legacy workspace file reference for create_from_file/import_file. Prefer fileId.', + }, + filter: { + type: 'object', + description: + 'MongoDB-style filter for query_rows, update_rows_by_filter, delete_rows_by_filter', + }, + limit: { + type: 'number', + description: 'Maximum rows to return or affect (optional, default 100)', + }, + name: { type: 'string', description: "Table name (required for 'create')" }, + newName: { type: 'string', description: 'New column name (required for rename_column)' }, + newType: { + type: 'string', + description: + 'New column type (optional for update_column). Types: string, number, boolean, date, json', + }, + offset: { + type: 'number', + description: 'Number of rows to skip (optional for query_rows, default 0)', + }, + outputFormat: { + type: 'string', + description: + 'Explicit format override for outputPath. Usually unnecessary — the file extension determines the format automatically. Only use this to force a different format than what the extension implies.', + enum: ['json', 'csv', 'txt', 'md', 'html'], + }, + outputPath: { + type: 'string', + description: + 'Pipe query_rows results directly to a NEW workspace file. The format is auto-inferred from the file extension: .csv → CSV, .json → JSON, .md → Markdown, etc. Use .csv for tabular exports. Use a flat path like "files/export.csv" — nested paths are not supported.', + }, + rowId: { + type: 'string', + description: 'Row ID (required for get_row, update_row, delete_row)', + }, + rowIds: { + type: 'array', + description: 'Array of row IDs to delete (for batch_delete_rows)', + }, + rows: { + type: 'array', + description: 'Array of row data objects (required for batch_insert_rows)', + }, + schema: { + type: 'object', + description: + "Table schema with columns array (required for 'create'). Each column: { name, type, unique? }", + }, + sort: { + type: 'object', + description: + "Sort specification as { field: 'asc' | 'desc' } (optional for query_rows)", + }, + tableId: { + type: 'string', + description: "Table ID (required for most operations except 'create')", + }, + unique: { + type: 'boolean', + description: 'Set column unique constraint (optional for update_column)', + }, + updates: { + type: 'array', + description: + 'Array of per-row updates: [{ rowId, data: { col: val } }] (for batch_update_rows)', + }, + values: { + type: 'object', + description: + 'Map of rowId to value for single-column batch update: { "rowId1": val1, "rowId2": val2 } (for batch_update_rows with columnName)', + }, + }, + }, + operation: { + type: 'string', + description: 'The operation to perform', + enum: [ + 'create', + 'create_from_file', + 'import_file', + 'get', + 'get_schema', + 'delete', + 'insert_row', + 'batch_insert_rows', + 'get_row', + 'query_rows', + 'update_row', + 'delete_row', + 'update_rows_by_filter', + 'delete_rows_by_filter', + 'batch_update_rows', + 'batch_delete_rows', + 'add_column', + 'rename_column', + 'delete_column', + 'update_column', + ], + }, + }, + required: ['operation', 'args'], + }, + resultSchema: { + type: 'object', + properties: { + data: { type: 'object', description: 'Operation-specific result payload.' }, + message: { type: 'string', description: 'Human-readable outcome summary.' }, + success: { type: 'boolean', description: 'Whether the operation succeeded.' }, + }, + required: ['success', 'message'], + }, requiresConfirmation: true, -}; +} export const Workflow: ToolCatalogEntry = { - id: "workflow", - name: "workflow", - executor: "subagent", - mode: "async", - parameters: {"properties":{"request":{"description":"A single sentence — the agent has full conversation context and VFS access. Do NOT look up IDs or pre-read data; the workflow agent does its own research. Example: 'move all the return letter workflows into a folder called Letters'.","type":"string"}},"required":["request"],"type":"object"}, - subagentId: "workflow", + id: 'workflow', + name: 'workflow', + executor: 'subagent', + mode: 'async', + parameters: { + properties: { + request: { + description: + "A single sentence — the agent has full conversation context and VFS access. Do NOT look up IDs or pre-read data; the workflow agent does its own research. Example: 'move all the return letter workflows into a folder called Letters'.", + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + subagentId: 'workflow', internal: true, -}; +} export const WorkspaceFile: ToolCatalogEntry = { - id: "workspace_file", - name: "workspace_file", - executor: "sim", - mode: "async", - parameters: {"type":"object","properties":{"operation":{"type":"string","description":"The file operation to perform.","enum":["append","update","patch","rename","delete"]},"target":{"type":"object","description":"Explicit file target. Use kind=file_id + fileId for all operations. Emit target keys in this order: kind, fileId, fileName.","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."},"fileName":{"type":"string","description":"Plain workspace filename including extension, e.g. \"main.py\" or \"report.docx\". Required when target.kind=new_file."}},"required":["kind"]},"title":{"type":"string","description":"Optional short UI label for create/append chunks, e.g. \"Chapter 1\" or \"Slide 3\"."},"contentType":{"type":"string","description":"Optional MIME type override. Usually omit and let the system infer from the target file extension.","enum":["text/markdown","text/html","text/plain","application/json","text/csv","application/vnd.openxmlformats-officedocument.presentationml.presentation","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/pdf"]},"edit":{"type":"object","description":"Patch metadata. Use strategy=search_replace for exact text replacement, or strategy=anchored for line-based inserts/replacements/deletions. Emit edit keys in this order: strategy, search, replace, replaceAll, mode, occurrence, before_anchor, after_anchor, anchor, start_anchor, end_anchor, content.","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."},"replace":{"type":"string","description":"Replacement text when strategy=search_replace."},"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":"Boundary line kept after inserted replacement content. Required for mode=replace_between."},"anchor":{"type":"string","description":"Anchor line after which new content is inserted. Required for mode=insert_after."},"start_anchor":{"type":"string","description":"First line to delete. Required for mode=delete_between."},"end_anchor":{"type":"string","description":"First line to keep after deletion. Required for mode=delete_between."},"content":{"type":"string","description":"Inserted or replacement content for anchored edits. Not used for delete_between."}}},"newName":{"type":"string","description":"New file name for rename. Must be a plain workspace filename like \"main.py\"."},"content":{"type":"string","description":"File content for append or update. For .pptx/.docx/.pdf this must be JavaScript source code for the corresponding generator runtime."}},"required":["operation","target"]}, - 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 file operation succeeded."}},"required":["success","message"]}, - requiredPermission: "write", -}; + id: 'workspace_file', + name: 'workspace_file', + executor: 'sim', + mode: 'async', + parameters: { + type: 'object', + properties: { + operation: { + type: 'string', + description: 'The file operation to perform.', + enum: ['append', 'update', 'patch', 'rename', 'delete'], + }, + target: { + 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.', + }, + fileName: { + type: 'string', + description: + 'Plain workspace filename including extension, e.g. "main.py" or "report.docx". Required when target.kind=new_file.', + }, + }, + required: ['kind'], + }, + title: { + type: 'string', + description: + 'Optional short UI label for create/append chunks, e.g. "Chapter 1" or "Slide 3".', + }, + contentType: { + type: 'string', + description: + 'Optional MIME type override. Usually omit and let the system infer from the target file extension.', + enum: [ + 'text/markdown', + 'text/html', + 'text/plain', + 'application/json', + 'text/csv', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/pdf', + ], + }, + edit: { + type: 'object', + 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: + 'Boundary line kept after inserted replacement content. Required for mode=replace_between.', + }, + anchor: { + type: 'string', + description: + 'Anchor line after which new content is inserted. Required for mode=insert_after.', + }, + start_anchor: { + type: 'string', + description: 'First line to delete. Required for mode=delete_between.', + }, + end_anchor: { + type: 'string', + description: 'First line to keep after deletion. Required for mode=delete_between.', + }, + }, + }, + newName: { + type: 'string', + description: 'New file name for rename. Must be a plain workspace filename like "main.py".', + }, + }, + required: ['operation', 'target'], + }, + 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 file operation succeeded.' }, + }, + required: ['success', 'message'], + }, + 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 TOOL_CATALOG: Record = { [Agent.id]: Agent, @@ -866,4 +2824,5 @@ export const TOOL_CATALOG: Record = { [UserTable.id]: UserTable, [Workflow.id]: Workflow, [WorkspaceFile.id]: WorkspaceFile, -}; + [EditContent.id]: EditContent, +} diff --git a/apps/sim/lib/copilot/generated/tool-schemas-v1.ts b/apps/sim/lib/copilot/generated/tool-schemas-v1.ts index 6771173efa..a4e11906e9 100644 --- a/apps/sim/lib/copilot/generated/tool-schemas-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-schemas-v1.ts @@ -5,2718 +5,2512 @@ export type JsonSchema = unknown export interface ToolRuntimeSchemaEntry { - parameters?: JsonSchema; - resultSchema?: JsonSchema; + parameters?: JsonSchema + resultSchema?: JsonSchema } export const TOOL_RUNTIME_SCHEMAS: Record = { - ["agent"]: { + agent: { parameters: { - "properties": { - "request": { - "description": "What tool/skill/MCP action is needed.", - "type": "string" - } + properties: { + request: { + description: 'What tool/skill/MCP action is needed.', + type: 'string', + }, }, - "required": [ - "request" - ], - "type": "object" + required: ['request'], + type: 'object', }, resultSchema: undefined, }, - ["auth"]: { + auth: { parameters: { - "properties": { - "request": { - "description": "What authentication/credential action is needed.", - "type": "string" - } + properties: { + request: { + description: 'What authentication/credential action is needed.', + type: 'string', + }, }, - "required": [ - "request" - ], - "type": "object" + required: ['request'], + type: 'object', }, resultSchema: undefined, }, - ["check_deployment_status"]: { + check_deployment_status: { parameters: { - "type": "object", - "properties": { - "workflowId": { - "type": "string", - "description": "Workflow ID to check (defaults to current workflow)" - } - } + type: 'object', + properties: { + workflowId: { + type: 'string', + description: 'Workflow ID to check (defaults to current workflow)', + }, + }, }, resultSchema: undefined, }, - ["complete_job"]: { + complete_job: { parameters: { - "type": "object", - "properties": { - "jobId": { - "type": "string", - "description": "The ID of the job to mark as completed." - } + type: 'object', + properties: { + jobId: { + type: 'string', + description: 'The ID of the job to mark as completed.', + }, }, - "required": [ - "jobId" - ] + required: ['jobId'], }, resultSchema: undefined, }, - ["context_write"]: { + context_write: { parameters: { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Full content to write to the file (replaces existing content)" + type: 'object', + properties: { + content: { + type: 'string', + description: 'Full content to write to the file (replaces existing content)', + }, + file_path: { + type: 'string', + description: "Path of the file to write (e.g. 'SESSION.md')", }, - "file_path": { - "type": "string", - "description": "Path of the file to write (e.g. 'SESSION.md')" - } }, - "required": [ - "file_path", - "content" - ] + required: ['file_path', 'content'], }, resultSchema: undefined, }, - ["crawl_website"]: { + crawl_website: { parameters: { - "type": "object", - "properties": { - "exclude_paths": { - "type": "array", - "description": "Skip URLs matching these patterns", - "items": { - "type": "string" - } + type: 'object', + properties: { + exclude_paths: { + type: 'array', + description: 'Skip URLs matching these patterns', + items: { + type: 'string', + }, }, - "include_paths": { - "type": "array", - "description": "Only crawl URLs matching these patterns", - "items": { - "type": "string" - } + include_paths: { + type: 'array', + description: 'Only crawl URLs matching these patterns', + items: { + type: 'string', + }, }, - "limit": { - "type": "number", - "description": "Maximum pages to crawl (default 10, max 50)" + limit: { + type: 'number', + description: 'Maximum pages to crawl (default 10, max 50)', }, - "max_depth": { - "type": "number", - "description": "How deep to follow links (default 2)" + max_depth: { + type: 'number', + description: 'How deep to follow links (default 2)', + }, + url: { + type: 'string', + description: 'Starting URL to crawl from', }, - "url": { - "type": "string", - "description": "Starting URL to crawl from" - } }, - "required": [ - "url" - ] + required: ['url'], }, resultSchema: undefined, }, - ["create_file"]: { + create_file: { parameters: { - "type": "object", - "properties": { - "contentType": { - "type": "string", - "description": "Optional MIME type override. Usually omit and let the system infer from the file extension." + type: 'object', + properties: { + contentType: { + type: 'string', + description: + 'Optional MIME type override. Usually omit and let the system infer from the file extension.', + }, + fileName: { + type: 'string', + description: + 'Plain workspace filename including extension, e.g. "main.py" or "report.md". Must not contain slashes.', }, - "fileName": { - "type": "string", - "description": "Plain workspace filename including extension, e.g. \"main.py\" or \"report.md\". Must not contain slashes." - } }, - "required": [ - "fileName" - ] + required: ['fileName'], }, resultSchema: { - "type": "object", - "properties": { - "data": { - "type": "object", - "description": "Contains id (the fileId) and name." + type: 'object', + properties: { + data: { + type: 'object', + description: 'Contains id (the fileId) and name.', }, - "message": { - "type": "string", - "description": "Human-readable outcome." + message: { + type: 'string', + description: 'Human-readable outcome.', + }, + success: { + type: 'boolean', + description: 'Whether the file was created.', }, - "success": { - "type": "boolean", - "description": "Whether the file was created." - } }, - "required": [ - "success", - "message" - ] + required: ['success', 'message'], }, }, - ["create_folder"]: { + create_folder: { parameters: { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Folder name." + type: 'object', + properties: { + name: { + type: 'string', + description: 'Folder name.', }, - "parentId": { - "type": "string", - "description": "Optional parent folder ID." + parentId: { + type: 'string', + description: 'Optional parent folder ID.', + }, + workspaceId: { + type: 'string', + description: 'Optional workspace ID.', }, - "workspaceId": { - "type": "string", - "description": "Optional workspace ID." - } }, - "required": [ - "name" - ] + required: ['name'], }, resultSchema: undefined, }, - ["create_job"]: { + create_job: { parameters: { - "type": "object", - "properties": { - "cron": { - "type": "string", - "description": "Cron expression for recurring jobs (e.g., '*/5 * * * *' for every 5 minutes, '0 9 * * *' for daily at 9 AM). Omit for one-time jobs." + type: 'object', + properties: { + cron: { + type: 'string', + description: + "Cron expression for recurring jobs (e.g., '*/5 * * * *' for every 5 minutes, '0 9 * * *' for daily at 9 AM). Omit for one-time jobs.", }, - "lifecycle": { - "type": "string", - "description": "'persistent' (default) or 'until_complete'. Until_complete jobs stop when complete_job is called after the success condition is met.", - "enum": [ - "persistent", - "until_complete" - ] + lifecycle: { + type: 'string', + description: + "'persistent' (default) or 'until_complete'. Until_complete jobs stop when complete_job is called after the success condition is met.", + enum: ['persistent', 'until_complete'], }, - "maxRuns": { - "type": "integer", - "description": "Maximum number of executions before the job auto-completes. Safety limit to prevent runaway polling." + maxRuns: { + type: 'integer', + description: + 'Maximum number of executions before the job auto-completes. Safety limit to prevent runaway polling.', }, - "prompt": { - "type": "string", - "description": "The prompt to execute when the job fires. This is sent to the Mothership as a user message." + prompt: { + type: 'string', + description: + 'The prompt to execute when the job fires. This is sent to the Mothership as a user message.', }, - "successCondition": { - "type": "string", - "description": "What must happen for the job to be considered complete. Used with until_complete lifecycle (e.g., 'John has replied to the partnership email')." + successCondition: { + type: 'string', + description: + "What must happen for the job to be considered complete. Used with until_complete lifecycle (e.g., 'John has replied to the partnership email').", }, - "time": { - "type": "string", - "description": "ISO 8601 datetime for one-time execution or as the start time for a cron schedule (e.g., '2026-03-06T09:00:00'). Include timezone offset or use the timezone parameter." + time: { + type: 'string', + description: + "ISO 8601 datetime for one-time execution or as the start time for a cron schedule (e.g., '2026-03-06T09:00:00'). Include timezone offset or use the timezone parameter.", }, - "timezone": { - "type": "string", - "description": "IANA timezone for the schedule (e.g., 'America/New_York', 'Europe/London'). Defaults to UTC." + timezone: { + type: 'string', + description: + "IANA timezone for the schedule (e.g., 'America/New_York', 'Europe/London'). Defaults to UTC.", + }, + title: { + type: 'string', + description: + "A short, descriptive title for the job (e.g., 'Email Poller', 'Daily Report'). Used as the display name.", }, - "title": { - "type": "string", - "description": "A short, descriptive title for the job (e.g., 'Email Poller', 'Daily Report'). Used as the display name." - } }, - "required": [ - "title", - "prompt" - ] + required: ['title', 'prompt'], }, resultSchema: undefined, }, - ["create_workflow"]: { + create_workflow: { parameters: { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "Optional workflow description." + type: 'object', + properties: { + description: { + type: 'string', + description: 'Optional workflow description.', }, - "folderId": { - "type": "string", - "description": "Optional folder ID." + folderId: { + type: 'string', + description: 'Optional folder ID.', }, - "name": { - "type": "string", - "description": "Workflow name." + name: { + type: 'string', + description: 'Workflow name.', + }, + workspaceId: { + type: 'string', + description: 'Optional workspace ID.', }, - "workspaceId": { - "type": "string", - "description": "Optional workspace ID." - } }, - "required": [ - "name" - ] + required: ['name'], }, resultSchema: undefined, }, - ["create_workspace_mcp_server"]: { + create_workspace_mcp_server: { parameters: { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "Optional description for the server" + type: 'object', + properties: { + description: { + type: 'string', + description: 'Optional description for the server', }, - "name": { - "type": "string", - "description": "Required: server name" + name: { + type: 'string', + description: 'Required: server name', + }, + workspaceId: { + type: 'string', + description: 'Workspace ID (defaults to current workspace)', }, - "workspaceId": { - "type": "string", - "description": "Workspace ID (defaults to current workspace)" - } }, - "required": [ - "name" - ] + required: ['name'], }, resultSchema: undefined, }, - ["debug"]: { + debug: { parameters: { - "properties": { - "context": { - "description": "Pre-gathered context: workflow state JSON, block schemas, error logs. The debug agent will skip re-reading anything included here.", - "type": "string" + properties: { + context: { + description: + 'Pre-gathered context: workflow state JSON, block schemas, error logs. The debug agent will skip re-reading anything included here.', + type: 'string', + }, + request: { + description: + 'What to debug. Include error messages, block IDs, and any context about the failure.', + type: 'string', }, - "request": { - "description": "What to debug. Include error messages, block IDs, and any context about the failure.", - "type": "string" - } }, - "required": [ - "request" - ], - "type": "object" + required: ['request'], + type: 'object', }, resultSchema: undefined, }, - ["delete_file"]: { + delete_file: { parameters: { - "type": "object", - "properties": { - "fileId": { - "type": "string", - "description": "Canonical workspace file ID of the file to delete." - } + type: 'object', + properties: { + fileId: { + type: 'string', + description: 'Canonical workspace file ID of the file to delete.', + }, }, - "required": [ - "fileId" - ] + required: ['fileId'], }, resultSchema: { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Human-readable outcome." + type: 'object', + properties: { + message: { + type: 'string', + description: 'Human-readable outcome.', + }, + success: { + type: 'boolean', + description: 'Whether the delete succeeded.', }, - "success": { - "type": "boolean", - "description": "Whether the delete succeeded." - } }, - "required": [ - "success", - "message" - ] + required: ['success', 'message'], }, }, - ["delete_folder"]: { + delete_folder: { parameters: { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "description": "The folder ID to delete." - } + type: 'object', + properties: { + folderId: { + type: 'string', + description: 'The folder ID to delete.', + }, }, - "required": [ - "folderId" - ] + required: ['folderId'], }, resultSchema: undefined, }, - ["delete_workflow"]: { + delete_workflow: { parameters: { - "type": "object", - "properties": { - "workflowId": { - "type": "string", - "description": "The workflow ID to delete." - } + type: 'object', + properties: { + workflowId: { + type: 'string', + description: 'The workflow ID to delete.', + }, }, - "required": [ - "workflowId" - ] + required: ['workflowId'], }, resultSchema: undefined, }, - ["delete_workspace_mcp_server"]: { + delete_workspace_mcp_server: { parameters: { - "type": "object", - "properties": { - "serverId": { - "type": "string", - "description": "Required: the MCP server ID to delete" - } + type: 'object', + properties: { + serverId: { + type: 'string', + description: 'Required: the MCP server ID to delete', + }, }, - "required": [ - "serverId" - ] + required: ['serverId'], }, resultSchema: undefined, }, - ["deploy"]: { + deploy: { parameters: { - "properties": { - "request": { - "description": "Detailed deployment instructions. Include deployment type (api/chat) and ALL user-specified options: identifier, title, description, authType, password, allowedEmails, welcomeMessage, outputConfigs (block outputs to display).", - "type": "string" - } + properties: { + request: { + description: + 'Detailed deployment instructions. Include deployment type (api/chat) and ALL user-specified options: identifier, title, description, authType, password, allowedEmails, welcomeMessage, outputConfigs (block outputs to display).', + type: 'string', + }, }, - "required": [ - "request" - ], - "type": "object" + required: ['request'], + type: 'object', }, resultSchema: undefined, }, - ["deploy_api"]: { + deploy_api: { parameters: { - "type": "object", - "properties": { - "action": { - "type": "string", - "description": "Whether to deploy or undeploy the API endpoint", - "enum": [ - "deploy", - "undeploy" - ], - "default": "deploy" + type: 'object', + properties: { + action: { + type: 'string', + description: 'Whether to deploy or undeploy the API endpoint', + enum: ['deploy', 'undeploy'], + default: 'deploy', }, - "workflowId": { - "type": "string", - "description": "Workflow ID to deploy (required in workspace context)" - } - } + workflowId: { + type: 'string', + description: 'Workflow ID to deploy (required in workspace context)', + }, + }, }, resultSchema: undefined, }, - ["deploy_chat"]: { + deploy_chat: { parameters: { - "type": "object", - "properties": { - "action": { - "type": "string", - "description": "Whether to deploy or undeploy the chat interface", - "enum": [ - "deploy", - "undeploy" - ], - "default": "deploy" + type: 'object', + properties: { + action: { + type: 'string', + description: 'Whether to deploy or undeploy the chat interface', + enum: ['deploy', 'undeploy'], + default: 'deploy', }, - "allowedEmails": { - "type": "array", - "description": "List of allowed emails/domains for email or SSO auth", - "items": { - "type": "string" - } + allowedEmails: { + type: 'array', + description: 'List of allowed emails/domains for email or SSO auth', + items: { + type: 'string', + }, }, - "authType": { - "type": "string", - "description": "Authentication type: public, password, email, or sso", - "enum": [ - "public", - "password", - "email", - "sso" - ], - "default": "public" + authType: { + type: 'string', + description: 'Authentication type: public, password, email, or sso', + enum: ['public', 'password', 'email', 'sso'], + default: 'public', }, - "description": { - "type": "string", - "description": "Optional description for the chat" + description: { + type: 'string', + description: 'Optional description for the chat', }, - "identifier": { - "type": "string", - "description": "URL slug for the chat (lowercase letters, numbers, hyphens only)" + identifier: { + type: 'string', + description: 'URL slug for the chat (lowercase letters, numbers, hyphens only)', }, - "outputConfigs": { - "type": "array", - "description": "Output configurations specifying which block outputs to display in chat", - "items": { - "type": "object", - "properties": { - "blockId": { - "type": "string", - "description": "The block UUID" + outputConfigs: { + type: 'array', + description: 'Output configurations specifying which block outputs to display in chat', + items: { + type: 'object', + properties: { + blockId: { + type: 'string', + description: 'The block UUID', }, - "path": { - "type": "string", - "description": "The output path (e.g. 'response', 'response.content')" - } - }, - "required": [ - "blockId", - "path" - ] - } - }, - "password": { - "type": "string", - "description": "Password for password-protected chats" - }, - "title": { - "type": "string", - "description": "Display title for the chat interface" - }, - "welcomeMessage": { - "type": "string", - "description": "Welcome message shown to users" - }, - "workflowId": { - "type": "string", - "description": "Workflow ID to deploy (required in workspace context)" - } - } - }, - resultSchema: undefined, - }, - ["deploy_mcp"]: { - parameters: { - "type": "object", - "properties": { - "parameterDescriptions": { - "type": "array", - "description": "Array of parameter descriptions for the tool", - "items": { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "Parameter description" + path: { + type: 'string', + description: "The output path (e.g. 'response', 'response.content')", }, - "name": { - "type": "string", - "description": "Parameter name" - } }, - "required": [ - "name", - "description" - ] - } + required: ['blockId', 'path'], + }, }, - "serverId": { - "type": "string", - "description": "Required: server ID from list_workspace_mcp_servers" + password: { + type: 'string', + description: 'Password for password-protected chats', }, - "toolDescription": { - "type": "string", - "description": "Description for the MCP tool" + title: { + type: 'string', + description: 'Display title for the chat interface', }, - "toolName": { - "type": "string", - "description": "Name for the MCP tool (defaults to workflow name)" + welcomeMessage: { + type: 'string', + description: 'Welcome message shown to users', + }, + workflowId: { + type: 'string', + description: 'Workflow ID to deploy (required in workspace context)', }, - "workflowId": { - "type": "string", - "description": "Workflow ID (defaults to active workflow)" - } }, - "required": [ - "serverId" - ] }, resultSchema: undefined, }, - ["download_to_workspace_file"]: { + deploy_mcp: { parameters: { - "type": "object", - "properties": { - "fileName": { - "type": "string", - "description": "Optional workspace file name to save as. If omitted, the name is inferred from the response or URL." - }, - "url": { - "type": "string", - "description": "Direct URL of the file to download, such as an image CDN URL ending in .png or .jpg" - } - }, - "required": [ - "url" - ] - }, - resultSchema: undefined, - }, - ["edit_workflow"]: { - parameters: { - "type": "object", - "properties": { - "operations": { - "type": "array", - "description": "Array of edit operations", - "items": { - "type": "object", - "properties": { - "block_id": { - "type": "string", - "description": "Block ID for the operation. For add operations, this will be the desired ID for the new block." + type: 'object', + properties: { + parameterDescriptions: { + type: 'array', + description: 'Array of parameter descriptions for the tool', + items: { + type: 'object', + properties: { + description: { + type: 'string', + description: 'Parameter description', }, - "operation_type": { - "type": "string", - "description": "Type of operation to perform", - "enum": [ - "add", - "edit", - "delete", - "insert_into_subflow", - "extract_from_subflow" - ] + name: { + type: 'string', + description: 'Parameter name', }, - "params": { - "type": "object", - "description": "Parameters for the operation. \nFor edit: {\"inputs\": {\"temperature\": 0.5}} NOT {\"subBlocks\": {\"temperature\": {\"value\": 0.5}}}\nFor add: {\"type\": \"agent\", \"name\": \"My Agent\", \"inputs\": {\"model\": \"gpt-4o\"}}\nFor delete: {} (empty object)" - } }, - "required": [ - "operation_type", - "block_id", - "params" - ] - } + required: ['name', 'description'], + }, + }, + serverId: { + type: 'string', + description: 'Required: server ID from list_workspace_mcp_servers', + }, + toolDescription: { + type: 'string', + description: 'Description for the MCP tool', + }, + toolName: { + type: 'string', + description: 'Name for the MCP tool (defaults to workflow name)', + }, + workflowId: { + type: 'string', + description: 'Workflow ID (defaults to active workflow)', }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID to edit. If not provided, uses the current workflow in context." - } }, - "required": [ - "operations" - ] + required: ['serverId'], }, resultSchema: undefined, }, - ["file"]: { + download_to_workspace_file: { parameters: { - "type": "object" - }, - resultSchema: undefined, - }, - ["function_execute"]: { - parameters: { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Code to execute. For JS: raw statements auto-wrapped in async context. For Python: full script. For shell: bash script with access to pre-installed CLI tools and workspace env vars as $VAR_NAME." + type: 'object', + properties: { + fileName: { + type: 'string', + description: + 'Optional workspace file name to save as. If omitted, the name is inferred from the response or URL.', }, - "inputFiles": { - "type": "array", - "description": "Canonical workspace file IDs to mount in the sandbox. Discover IDs via read(\"files/{name}/meta.json\") or glob(\"files/by-id/*/meta.json\"). Mounted path: /home/user/files/{fileId}/{originalName}. Example: [\"wf_123\"]", - "items": { - "type": "string" - } + url: { + type: 'string', + description: + 'Direct URL of the file to download, such as an image CDN URL ending in .png or .jpg', }, - "inputTables": { - "type": "array", - "description": "Table IDs to mount as CSV files in the sandbox. Each table appears at /home/user/tables/{tableId}.csv with a header row. Example: [\"tbl_abc123\"]", - "items": { - "type": "string" - } - }, - "language": { - "type": "string", - "description": "Execution language.", - "enum": [ - "javascript", - "python", - "shell" - ] - }, - "outputFormat": { - "type": "string", - "description": "Format for outputPath. Determines how the code result is serialized. If omitted, inferred from outputPath file extension.", - "enum": [ - "json", - "csv", - "txt", - "md", - "html" - ] - }, - "outputMimeType": { - "type": "string", - "description": "MIME type for outputSandboxPath export. Required for binary files: image/png, image/jpeg, application/pdf, etc. Omit for text files." - }, - "outputPath": { - "type": "string", - "description": "Pipe output directly to a NEW workspace file instead of returning in context. ALWAYS use this instead of a separate workspace_file write call. Use a flat path like \"files/result.json\" — nested paths are not supported." - }, - "outputSandboxPath": { - "type": "string", - "description": "Path to a file created inside the sandbox that should be exported to the workspace. Use together with outputPath." - }, - "outputTable": { - "type": "string", - "description": "Table ID to overwrite with the code's return value. Code MUST return an array of objects where keys match column names. All existing rows are replaced. Example: \"tbl_abc123\"" - } }, - "required": [ - "code" - ] + required: ['url'], }, resultSchema: undefined, }, - ["generate_api_key"]: { + edit_workflow: { parameters: { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "A descriptive name for the API key (e.g., 'production-key', 'dev-testing')." - }, - "workspaceId": { - "type": "string", - "description": "Optional workspace ID. Defaults to user's default workspace." - } - }, - "required": [ - "name" - ] - }, - resultSchema: undefined, - }, - ["generate_image"]: { - parameters: { - "type": "object", - "properties": { - "aspectRatio": { - "type": "string", - "description": "Aspect ratio for the generated image.", - "enum": [ - "1:1", - "16:9", - "9:16", - "4:3", - "3:4" - ] - }, - "fileName": { - "type": "string", - "description": "Output file name. Defaults to \"generated-image.png\". Workspace files are flat, so pass a plain file name, not a nested path." - }, - "overwriteFileId": { - "type": "string", - "description": "If provided, overwrites the existing workspace file with this ID instead of creating a new file. Use this when the user asks to update or redo a previously generated image. The file ID is returned by previous generate_image or generate_visualization calls (fileId field), or can be found via read(\"files/by-id/{fileId}/meta.json\")." - }, - "prompt": { - "type": "string", - "description": "Detailed text description of the image to generate, or editing instructions when used with editFileId." - }, - "referenceFileIds": { - "type": "array", - "description": "File IDs of workspace images to include as context for the generation. All images are sent alongside the prompt. Use for: editing a single image (1 file), compositing multiple images together (2+ files), style transfer, face swapping, etc. Order matters — list the primary/base image first.", - "items": { - "type": "string" - } - } - }, - "required": [ - "prompt" - ] - }, - resultSchema: undefined, - }, - ["generate_visualization"]: { - parameters: { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Python code that generates a visualization using matplotlib. MUST call plt.savefig('/home/user/output.png', dpi=150, bbox_inches='tight') to produce output." - }, - "fileName": { - "type": "string", - "description": "Output file name. Defaults to \"chart.png\". Workspace files are flat, so pass a plain file name, not a nested path." - }, - "inputFiles": { - "type": "array", - "description": "Canonical workspace file IDs to mount in the sandbox. Discover IDs via read(\"files/{name}/meta.json\") or glob(\"files/by-id/*/meta.json\"). Mounted path: /home/user/files/{fileId}/{originalName}.", - "items": { - "type": "string" - } - }, - "inputTables": { - "type": "array", - "description": "Table IDs to mount as CSV files in the sandbox. Each table appears at /home/user/tables/{tableId}.csv with a header row. Read with pandas: pd.read_csv('/home/user/tables/tbl_xxx.csv')", - "items": { - "type": "string" - } - }, - "overwriteFileId": { - "type": "string", - "description": "If provided, overwrites the existing workspace file with this ID instead of creating a new file. Use this when the user asks to update or redo a previously generated chart. The file ID is returned by previous generate_visualization or generate_image calls (fileId field), or can be found via read(\"files/by-id/{fileId}/meta.json\")." - } - }, - "required": [ - "code" - ] - }, - resultSchema: undefined, - }, - ["get_block_outputs"]: { - parameters: { - "type": "object", - "properties": { - "blockIds": { - "type": "array", - "description": "Optional array of block UUIDs. If provided, returns outputs only for those blocks. If not provided, returns outputs for all blocks in the workflow.", - "items": { - "type": "string" - } - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID. If not provided, uses the current workflow in context." - } - } - }, - resultSchema: undefined, - }, - ["get_block_upstream_references"]: { - parameters: { - "type": "object", - "properties": { - "blockIds": { - "type": "array", - "description": "Required array of block UUIDs (minimum 1). Returns what each block can reference based on its position in the workflow graph.", - "items": { - "type": "string" - } - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID. If not provided, uses the current workflow in context." - } - }, - "required": [ - "blockIds" - ] - }, - resultSchema: undefined, - }, - ["get_deployed_workflow_state"]: { - parameters: { - "type": "object", - "properties": { - "workflowId": { - "type": "string", - "description": "Optional workflow ID. If not provided, uses the current workflow in context." - } - } - }, - resultSchema: undefined, - }, - ["get_deployment_version"]: { - parameters: { - "type": "object", - "properties": { - "version": { - "type": "number", - "description": "The deployment version number" - }, - "workflowId": { - "type": "string", - "description": "The workflow ID" - } - }, - "required": [ - "workflowId", - "version" - ] - }, - resultSchema: undefined, - }, - ["get_execution_summary"]: { - parameters: { - "type": "object", - "properties": { - "limit": { - "type": "number", - "description": "Max number of executions to return (default: 10, max: 20)." - }, - "status": { - "type": "string", - "description": "Filter by status: 'success', 'error', or 'all' (default: 'all').", - "enum": [ - "success", - "error", - "all" - ] - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID. If omitted, returns executions across all workflows in the workspace." - }, - "workspaceId": { - "type": "string", - "description": "Workspace ID to scope executions to." - } - }, - "required": [ - "workspaceId" - ] - }, - resultSchema: undefined, - }, - ["get_job_logs"]: { - parameters: { - "type": "object", - "properties": { - "executionId": { - "type": "string", - "description": "Optional execution ID for a specific run." - }, - "includeDetails": { - "type": "boolean", - "description": "Include tool calls, outputs, and cost details." - }, - "jobId": { - "type": "string", - "description": "The job (schedule) ID to get logs for." - }, - "limit": { - "type": "number", - "description": "Max number of entries (default: 3, max: 5)" - } - }, - "required": [ - "jobId" - ] - }, - resultSchema: undefined, - }, - ["get_page_contents"]: { - parameters: { - "type": "object", - "properties": { - "include_highlights": { - "type": "boolean", - "description": "Include key highlights (default false)" - }, - "include_summary": { - "type": "boolean", - "description": "Include AI-generated summary (default false)" - }, - "include_text": { - "type": "boolean", - "description": "Include full page text (default true)" - }, - "urls": { - "type": "array", - "description": "URLs to get content from (max 10)", - "items": { - "type": "string" - } - } - }, - "required": [ - "urls" - ] - }, - resultSchema: undefined, - }, - ["get_platform_actions"]: { - parameters: { - "type": "object", - "properties": {} - }, - resultSchema: undefined, - }, - ["get_workflow_data"]: { - parameters: { - "type": "object", - "properties": { - "data_type": { - "type": "string", - "description": "The type of workflow data to retrieve", - "enum": [ - "global_variables", - "custom_tools", - "mcp_tools", - "files" - ] - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID. If not provided, uses the current workflow in context." - } - }, - "required": [ - "data_type" - ] - }, - resultSchema: undefined, - }, - ["get_workflow_logs"]: { - parameters: { - "type": "object", - "properties": { - "executionId": { - "type": "string", - "description": "Optional execution ID to get logs for a specific execution. Use with get_execution_summary to find execution IDs first." - }, - "includeDetails": { - "type": "boolean", - "description": "Include detailed info" - }, - "limit": { - "type": "number", - "description": "Max number of entries (hard limit: 3)" - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID. If not provided, uses the current workflow in context." - } - } - }, - resultSchema: undefined, - }, - ["glob"]: { - parameters: { - "type": "object", - "properties": { - "pattern": { - "type": "string", - "description": "Glob pattern to match file paths. Supports * (any segment) and ** (any depth)." - }, - "title": { - "type": "string", - "description": "Short human-readable label shown in the UI while this search runs (e.g. 'Finding workflow configs', 'Listing knowledge bases')." - } - }, - "required": [ - "pattern" - ] - }, - resultSchema: undefined, - }, - ["grep"]: { - parameters: { - "type": "object", - "properties": { - "context": { - "type": "number", - "description": "Number of lines to show before and after each match. Only applies to output_mode 'content'." - }, - "ignoreCase": { - "type": "boolean", - "description": "Case insensitive search (default false)." - }, - "lineNumbers": { - "type": "boolean", - "description": "Include line numbers in output (default true). Only applies to output_mode 'content'." - }, - "maxResults": { - "type": "number", - "description": "Maximum number of matches to return (default 50)." - }, - "output_mode": { - "type": "string", - "description": "Output mode: 'content' shows matching lines (default), 'files_with_matches' shows only file paths, 'count' shows match counts per file.", - "enum": [ - "content", - "files_with_matches", - "count" - ] - }, - "path": { - "type": "string", - "description": "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": { - "type": "string", - "description": "Short human-readable label shown in the UI while this search runs (e.g. 'Searching Slack integrations', 'Finding deployed workflows')." - } - }, - "required": [ - "pattern" - ] - }, - resultSchema: undefined, - }, - ["job"]: { - parameters: { - "properties": { - "request": { - "description": "What job action is needed.", - "type": "string" - } - }, - "required": [ - "request" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["knowledge"]: { - parameters: { - "properties": { - "request": { - "description": "What knowledge base action is needed.", - "type": "string" - } - }, - "required": [ - "request" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["knowledge_base"]: { - parameters: { - "type": "object", - "properties": { - "args": { - "type": "object", - "description": "Arguments for the operation", - "properties": { - "apiKey": { - "type": "string", - "description": "API key for API-key-based connectors (required when connector auth mode is apiKey)" + type: 'object', + properties: { + operations: { + type: 'array', + description: 'Array of edit operations', + items: { + type: 'object', + properties: { + block_id: { + type: 'string', + description: + 'Block ID for the operation. For add operations, this will be the desired ID for the new block.', + }, + operation_type: { + type: 'string', + description: 'Type of operation to perform', + enum: ['add', 'edit', 'delete', 'insert_into_subflow', 'extract_from_subflow'], + }, + params: { + type: 'object', + description: + 'Parameters for the operation. \nFor edit: {"inputs": {"temperature": 0.5}} NOT {"subBlocks": {"temperature": {"value": 0.5}}}\nFor add: {"type": "agent", "name": "My Agent", "inputs": {"model": "gpt-4o"}}\nFor delete: {} (empty object)', + }, }, - "chunkingConfig": { - "type": "object", - "description": "Chunking configuration (optional for 'create')", - "properties": { - "maxSize": { - "type": "number", - "description": "Maximum chunk size (100-4000, default: 1024)", - "default": 1024 + required: ['operation_type', 'block_id', 'params'], + }, + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to edit. If not provided, uses the current workflow in context.', + }, + }, + required: ['operations'], + }, + resultSchema: undefined, + }, + file: { + parameters: { + type: 'object', + }, + resultSchema: undefined, + }, + function_execute: { + parameters: { + type: 'object', + properties: { + code: { + type: 'string', + description: + 'Code to execute. For JS: raw statements auto-wrapped in async context. For Python: full script. For shell: bash script with access to pre-installed CLI tools and workspace env vars as $VAR_NAME.', + }, + inputFiles: { + type: 'array', + description: + 'Canonical workspace file IDs to mount in the sandbox. Discover IDs via read("files/{name}/meta.json") or glob("files/by-id/*/meta.json"). Mounted path: /home/user/files/{fileId}/{originalName}. Example: ["wf_123"]', + items: { + type: 'string', + }, + }, + inputTables: { + type: 'array', + description: + 'Table IDs to mount as CSV files in the sandbox. Each table appears at /home/user/tables/{tableId}.csv with a header row. Example: ["tbl_abc123"]', + items: { + type: 'string', + }, + }, + language: { + type: 'string', + description: 'Execution language.', + enum: ['javascript', 'python', 'shell'], + }, + outputFormat: { + type: 'string', + description: + 'Format for outputPath. Determines how the code result is serialized. If omitted, inferred from outputPath file extension.', + enum: ['json', 'csv', 'txt', 'md', 'html'], + }, + outputMimeType: { + type: 'string', + description: + 'MIME type for outputSandboxPath export. Required for binary files: image/png, image/jpeg, application/pdf, etc. Omit for text files.', + }, + outputPath: { + type: 'string', + description: + 'Pipe output directly to a NEW workspace file instead of returning in context. ALWAYS use this instead of a separate workspace_file write call. Use a flat path like "files/result.json" — nested paths are not supported.', + }, + outputSandboxPath: { + type: 'string', + description: + 'Path to a file created inside the sandbox that should be exported to the workspace. Use together with outputPath.', + }, + outputTable: { + type: 'string', + description: + 'Table ID to overwrite with the code\'s return value. Code MUST return an array of objects where keys match column names. All existing rows are replaced. Example: "tbl_abc123"', + }, + }, + required: ['code'], + }, + resultSchema: undefined, + }, + generate_api_key: { + parameters: { + type: 'object', + properties: { + name: { + type: 'string', + description: + "A descriptive name for the API key (e.g., 'production-key', 'dev-testing').", + }, + workspaceId: { + type: 'string', + description: "Optional workspace ID. Defaults to user's default workspace.", + }, + }, + required: ['name'], + }, + resultSchema: undefined, + }, + generate_image: { + parameters: { + type: 'object', + properties: { + aspectRatio: { + type: 'string', + description: 'Aspect ratio for the generated image.', + enum: ['1:1', '16:9', '9:16', '4:3', '3:4'], + }, + fileName: { + type: 'string', + description: + 'Output file name. Defaults to "generated-image.png". Workspace files are flat, so pass a plain file name, not a nested path.', + }, + overwriteFileId: { + type: 'string', + description: + 'If provided, overwrites the existing workspace file with this ID instead of creating a new file. Use this when the user asks to update or redo a previously generated image. The file ID is returned by previous generate_image or generate_visualization calls (fileId field), or can be found via read("files/by-id/{fileId}/meta.json").', + }, + prompt: { + type: 'string', + description: + 'Detailed text description of the image to generate, or editing instructions when used with editFileId.', + }, + referenceFileIds: { + type: 'array', + description: + 'File IDs of workspace images to include as context for the generation. All images are sent alongside the prompt. Use for: editing a single image (1 file), compositing multiple images together (2+ files), style transfer, face swapping, etc. Order matters — list the primary/base image first.', + items: { + type: 'string', + }, + }, + }, + required: ['prompt'], + }, + resultSchema: undefined, + }, + generate_visualization: { + parameters: { + type: 'object', + properties: { + code: { + type: 'string', + description: + "Python code that generates a visualization using matplotlib. MUST call plt.savefig('/home/user/output.png', dpi=150, bbox_inches='tight') to produce output.", + }, + fileName: { + type: 'string', + description: + 'Output file name. Defaults to "chart.png". Workspace files are flat, so pass a plain file name, not a nested path.', + }, + inputFiles: { + type: 'array', + description: + 'Canonical workspace file IDs to mount in the sandbox. Discover IDs via read("files/{name}/meta.json") or glob("files/by-id/*/meta.json"). Mounted path: /home/user/files/{fileId}/{originalName}.', + items: { + type: 'string', + }, + }, + inputTables: { + type: 'array', + description: + "Table IDs to mount as CSV files in the sandbox. Each table appears at /home/user/tables/{tableId}.csv with a header row. Read with pandas: pd.read_csv('/home/user/tables/tbl_xxx.csv')", + items: { + type: 'string', + }, + }, + overwriteFileId: { + type: 'string', + description: + 'If provided, overwrites the existing workspace file with this ID instead of creating a new file. Use this when the user asks to update or redo a previously generated chart. The file ID is returned by previous generate_visualization or generate_image calls (fileId field), or can be found via read("files/by-id/{fileId}/meta.json").', + }, + }, + required: ['code'], + }, + resultSchema: undefined, + }, + get_block_outputs: { + parameters: { + type: 'object', + properties: { + blockIds: { + type: 'array', + description: + 'Optional array of block UUIDs. If provided, returns outputs only for those blocks. If not provided, returns outputs for all blocks in the workflow.', + items: { + type: 'string', + }, + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + }, + resultSchema: undefined, + }, + get_block_upstream_references: { + parameters: { + type: 'object', + properties: { + blockIds: { + type: 'array', + description: + 'Required array of block UUIDs (minimum 1). Returns what each block can reference based on its position in the workflow graph.', + items: { + type: 'string', + }, + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + required: ['blockIds'], + }, + resultSchema: undefined, + }, + get_deployed_workflow_state: { + parameters: { + type: 'object', + properties: { + workflowId: { + type: 'string', + description: + 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + }, + resultSchema: undefined, + }, + get_deployment_version: { + parameters: { + type: 'object', + properties: { + version: { + type: 'number', + description: 'The deployment version number', + }, + workflowId: { + type: 'string', + description: 'The workflow ID', + }, + }, + required: ['workflowId', 'version'], + }, + resultSchema: undefined, + }, + get_execution_summary: { + parameters: { + type: 'object', + properties: { + limit: { + type: 'number', + description: 'Max number of executions to return (default: 10, max: 20).', + }, + status: { + type: 'string', + description: "Filter by status: 'success', 'error', or 'all' (default: 'all').", + enum: ['success', 'error', 'all'], + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID. If omitted, returns executions across all workflows in the workspace.', + }, + workspaceId: { + type: 'string', + description: 'Workspace ID to scope executions to.', + }, + }, + required: ['workspaceId'], + }, + resultSchema: undefined, + }, + get_job_logs: { + parameters: { + type: 'object', + properties: { + executionId: { + type: 'string', + description: 'Optional execution ID for a specific run.', + }, + includeDetails: { + type: 'boolean', + description: 'Include tool calls, outputs, and cost details.', + }, + jobId: { + type: 'string', + description: 'The job (schedule) ID to get logs for.', + }, + limit: { + type: 'number', + description: 'Max number of entries (default: 3, max: 5)', + }, + }, + required: ['jobId'], + }, + resultSchema: undefined, + }, + get_page_contents: { + parameters: { + type: 'object', + properties: { + include_highlights: { + type: 'boolean', + description: 'Include key highlights (default false)', + }, + include_summary: { + type: 'boolean', + description: 'Include AI-generated summary (default false)', + }, + include_text: { + type: 'boolean', + description: 'Include full page text (default true)', + }, + urls: { + type: 'array', + description: 'URLs to get content from (max 10)', + items: { + type: 'string', + }, + }, + }, + required: ['urls'], + }, + resultSchema: undefined, + }, + get_platform_actions: { + parameters: { + type: 'object', + properties: {}, + }, + resultSchema: undefined, + }, + get_workflow_data: { + parameters: { + type: 'object', + properties: { + data_type: { + type: 'string', + description: 'The type of workflow data to retrieve', + enum: ['global_variables', 'custom_tools', 'mcp_tools', 'files'], + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + required: ['data_type'], + }, + resultSchema: undefined, + }, + get_workflow_logs: { + parameters: { + type: 'object', + properties: { + executionId: { + type: 'string', + description: + 'Optional execution ID to get logs for a specific execution. Use with get_execution_summary to find execution IDs first.', + }, + includeDetails: { + type: 'boolean', + description: 'Include detailed info', + }, + limit: { + type: 'number', + description: 'Max number of entries (hard limit: 3)', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + }, + resultSchema: undefined, + }, + glob: { + parameters: { + type: 'object', + properties: { + pattern: { + type: 'string', + description: + 'Glob pattern to match file paths. Supports * (any segment) and ** (any depth).', + }, + title: { + type: 'string', + description: + "Short human-readable label shown in the UI while this search runs (e.g. 'Finding workflow configs', 'Listing knowledge bases').", + }, + }, + required: ['pattern'], + }, + resultSchema: undefined, + }, + grep: { + parameters: { + type: 'object', + properties: { + context: { + type: 'number', + description: + "Number of lines to show before and after each match. Only applies to output_mode 'content'.", + }, + ignoreCase: { + type: 'boolean', + description: 'Case insensitive search (default false).', + }, + lineNumbers: { + type: 'boolean', + description: + "Include line numbers in output (default true). Only applies to output_mode 'content'.", + }, + maxResults: { + type: 'number', + description: 'Maximum number of matches to return (default 50).', + }, + output_mode: { + type: 'string', + description: + "Output mode: 'content' shows matching lines (default), 'files_with_matches' shows only file paths, 'count' shows match counts per file.", + enum: ['content', 'files_with_matches', 'count'], + }, + path: { + type: 'string', + description: + "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: { + type: 'string', + description: + "Short human-readable label shown in the UI while this search runs (e.g. 'Searching Slack integrations', 'Finding deployed workflows').", + }, + }, + required: ['pattern'], + }, + resultSchema: undefined, + }, + job: { + parameters: { + properties: { + request: { + description: 'What job action is needed.', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + resultSchema: undefined, + }, + knowledge: { + parameters: { + properties: { + request: { + description: 'What knowledge base action is needed.', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + resultSchema: undefined, + }, + knowledge_base: { + parameters: { + type: 'object', + properties: { + args: { + type: 'object', + description: 'Arguments for the operation', + properties: { + apiKey: { + type: 'string', + description: + 'API key for API-key-based connectors (required when connector auth mode is apiKey)', + }, + chunkingConfig: { + type: 'object', + description: "Chunking configuration (optional for 'create')", + properties: { + maxSize: { + type: 'number', + description: 'Maximum chunk size (100-4000, default: 1024)', + default: 1024, }, - "minSize": { - "type": "number", - "description": "Minimum chunk size (1-2000, default: 1)", - "default": 1 + minSize: { + type: 'number', + description: 'Minimum chunk size (1-2000, default: 1)', + default: 1, }, - "overlap": { - "type": "number", - "description": "Overlap between chunks (0-500, default: 200)", - "default": 200 - } - } + overlap: { + type: 'number', + description: 'Overlap between chunks (0-500, default: 200)', + default: 200, + }, + }, }, - "connectorId": { - "type": "string", - "description": "Connector ID (required for update_connector, delete_connector, sync_connector)" + connectorId: { + type: 'string', + description: + 'Connector ID (required for update_connector, delete_connector, sync_connector)', }, - "connectorStatus": { - "type": "string", - "description": "Connector status (optional for update_connector)", - "enum": [ - "active", - "paused" - ] + connectorStatus: { + type: 'string', + description: 'Connector status (optional for update_connector)', + enum: ['active', 'paused'], }, - "connectorType": { - "type": "string", - "description": "Connector type from registry, e.g. 'confluence', 'google_drive', 'notion' (required for add_connector). Read knowledgebases/connectors/{type}.json for the config schema." + connectorType: { + type: 'string', + description: + "Connector type from registry, e.g. 'confluence', 'google_drive', 'notion' (required for add_connector). Read knowledgebases/connectors/{type}.json for the config schema.", }, - "credentialId": { - "type": "string", - "description": "OAuth credential ID from environment/credentials.json (required for OAuth connectors)" + credentialId: { + type: 'string', + description: + 'OAuth credential ID from environment/credentials.json (required for OAuth connectors)', }, - "description": { - "type": "string", - "description": "Description of the knowledge base (optional for 'create')" + description: { + type: 'string', + description: "Description of the knowledge base (optional for 'create')", }, - "disabledTagIds": { - "type": "array", - "description": "Tag definition IDs to opt out of (optional for add_connector). See tagDefinitions in the connector schema." + disabledTagIds: { + type: 'array', + description: + 'Tag definition IDs to opt out of (optional for add_connector). See tagDefinitions in the connector schema.', }, - "documentId": { - "type": "string", - "description": "Document ID (required for delete_document, update_document)" + documentId: { + type: 'string', + description: 'Document ID (required for delete_document, update_document)', }, - "enabled": { - "type": "boolean", - "description": "Enable/disable a document (optional for update_document)" + enabled: { + type: 'boolean', + description: 'Enable/disable a document (optional for update_document)', }, - "fileId": { - "type": "string", - "description": "Canonical workspace file ID to add as a document (preferred for add_file). Discover via read(\"files/{name}/meta.json\") or glob(\"files/by-id/*/meta.json\")." + fileId: { + type: 'string', + description: + 'Canonical workspace file ID to add as a document (preferred for add_file). Discover via read("files/{name}/meta.json") or glob("files/by-id/*/meta.json").', }, - "filePath": { - "type": "string", - "description": "Legacy workspace file reference for add_file. Prefer fileId." + filePath: { + type: 'string', + description: 'Legacy workspace file reference for add_file. Prefer fileId.', }, - "filename": { - "type": "string", - "description": "New filename for a document (optional for update_document)" + filename: { + type: 'string', + description: 'New filename for a document (optional for update_document)', }, - "knowledgeBaseId": { - "type": "string", - "description": "Knowledge base ID (required for get, query, add_file, list_tags, create_tag, get_tag_usage)" + knowledgeBaseId: { + type: 'string', + description: + 'Knowledge base ID (required for get, query, add_file, list_tags, create_tag, get_tag_usage)', }, - "name": { - "type": "string", - "description": "Name of the knowledge base (required for 'create')" + name: { + type: 'string', + description: "Name of the knowledge base (required for 'create')", }, - "query": { - "type": "string", - "description": "Search query text (required for 'query')" + query: { + type: 'string', + description: "Search query text (required for 'query')", }, - "sourceConfig": { - "type": "object", - "description": "Connector-specific configuration matching the configFields in knowledgebases/connectors/{type}.json" + sourceConfig: { + type: 'object', + description: + 'Connector-specific configuration matching the configFields in knowledgebases/connectors/{type}.json', }, - "syncIntervalMinutes": { - "type": "number", - "description": "Sync interval in minutes: 60 (hourly), 360 (6h), 1440 (daily), 10080 (weekly), 0 (manual only). Default: 1440", - "default": 1440 + syncIntervalMinutes: { + type: 'number', + description: + 'Sync interval in minutes: 60 (hourly), 360 (6h), 1440 (daily), 10080 (weekly), 0 (manual only). Default: 1440', + default: 1440, }, - "tagDefinitionId": { - "type": "string", - "description": "Tag definition ID (required for update_tag, delete_tag)" + tagDefinitionId: { + type: 'string', + description: 'Tag definition ID (required for update_tag, delete_tag)', }, - "tagDisplayName": { - "type": "string", - "description": "Display name for the tag (required for create_tag, optional for update_tag)" + tagDisplayName: { + type: 'string', + description: + 'Display name for the tag (required for create_tag, optional for update_tag)', }, - "tagFieldType": { - "type": "string", - "description": "Field type: text, number, date, boolean (optional for create_tag, defaults to text)", - "enum": [ - "text", - "number", - "date", - "boolean" - ] + tagFieldType: { + type: 'string', + description: + 'Field type: text, number, date, boolean (optional for create_tag, defaults to text)', + enum: ['text', 'number', 'date', 'boolean'], }, - "topK": { - "type": "number", - "description": "Number of results to return (1-50, default: 5)", - "default": 5 + topK: { + type: 'number', + description: 'Number of results to return (1-50, default: 5)', + default: 5, }, - "workspaceId": { - "type": "string", - "description": "Workspace ID (required for 'create', optional filter for 'list')" - } - } + workspaceId: { + type: 'string', + description: "Workspace ID (required for 'create', optional filter for 'list')", + }, + }, + }, + operation: { + type: 'string', + description: 'The operation to perform', + enum: [ + 'create', + 'get', + 'query', + 'add_file', + 'update', + 'delete', + 'delete_document', + 'update_document', + 'list_tags', + 'create_tag', + 'update_tag', + 'delete_tag', + 'get_tag_usage', + 'add_connector', + 'update_connector', + 'delete_connector', + 'sync_connector', + ], }, - "operation": { - "type": "string", - "description": "The operation to perform", - "enum": [ - "create", - "get", - "query", - "add_file", - "update", - "delete", - "delete_document", - "update_document", - "list_tags", - "create_tag", - "update_tag", - "delete_tag", - "get_tag_usage", - "add_connector", - "update_connector", - "delete_connector", - "sync_connector" - ] - } }, - "required": [ - "operation", - "args" - ] + required: ['operation', 'args'], }, resultSchema: { - "type": "object", - "properties": { - "data": { - "type": "object", - "description": "Operation-specific result payload." + type: 'object', + properties: { + data: { + type: 'object', + description: 'Operation-specific result payload.', }, - "message": { - "type": "string", - "description": "Human-readable outcome summary." + message: { + type: 'string', + description: 'Human-readable outcome summary.', + }, + success: { + type: 'boolean', + description: 'Whether the operation succeeded.', }, - "success": { - "type": "boolean", - "description": "Whether the operation succeeded." - } }, - "required": [ - "success", - "message" - ] + required: ['success', 'message'], }, }, - ["list_folders"]: { + list_folders: { parameters: { - "type": "object", - "properties": { - "workspaceId": { - "type": "string", - "description": "Optional workspace ID to list folders for." - } - } - }, - resultSchema: undefined, - }, - ["list_user_workspaces"]: { - parameters: { - "type": "object", - "properties": {} - }, - resultSchema: undefined, - }, - ["list_workspace_mcp_servers"]: { - parameters: { - "type": "object", - "properties": { - "workspaceId": { - "type": "string", - "description": "Workspace ID (defaults to current workspace)" - } - } - }, - resultSchema: undefined, - }, - ["manage_credential"]: { - parameters: { - "type": "object", - "properties": { - "credentialId": { - "type": "string", - "description": "The credential ID (from environment/credentials.json)" + type: 'object', + properties: { + workspaceId: { + type: 'string', + description: 'Optional workspace ID to list folders for.', }, - "displayName": { - "type": "string", - "description": "New display name (required for rename)" - }, - "operation": { - "type": "string", - "description": "The operation to perform", - "enum": [ - "rename", - "delete" - ] - } }, - "required": [ - "operation", - "credentialId" - ] }, resultSchema: undefined, }, - ["manage_custom_tool"]: { + list_user_workspaces: { parameters: { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "The JavaScript code that executes when the tool is called (required for add). Parameters from schema are available as variables. Function body only - no signature or wrapping braces." + type: 'object', + properties: {}, + }, + resultSchema: undefined, + }, + list_workspace_mcp_servers: { + parameters: { + type: 'object', + properties: { + workspaceId: { + type: 'string', + description: 'Workspace ID (defaults to current workspace)', }, - "operation": { - "type": "string", - "description": "The operation to perform: 'add', 'edit', 'list', or 'delete'", - "enum": [ - "add", - "edit", - "delete", - "list" - ] + }, + }, + resultSchema: undefined, + }, + manage_credential: { + parameters: { + type: 'object', + properties: { + credentialId: { + type: 'string', + description: 'The credential ID (from environment/credentials.json)', }, - "schema": { - "type": "object", - "description": "The tool schema in OpenAI function calling format (required for add).", - "properties": { - "function": { - "type": "object", - "description": "The function definition", - "properties": { - "description": { - "type": "string", - "description": "What the function does" + displayName: { + type: 'string', + description: 'New display name (required for rename)', + }, + operation: { + type: 'string', + description: 'The operation to perform', + enum: ['rename', 'delete'], + }, + }, + required: ['operation', 'credentialId'], + }, + resultSchema: undefined, + }, + manage_custom_tool: { + parameters: { + type: 'object', + properties: { + code: { + type: 'string', + description: + 'The JavaScript code that executes when the tool is called (required for add). Parameters from schema are available as variables. Function body only - no signature or wrapping braces.', + }, + operation: { + type: 'string', + description: "The operation to perform: 'add', 'edit', 'list', or 'delete'", + enum: ['add', 'edit', 'delete', 'list'], + }, + schema: { + type: 'object', + description: 'The tool schema in OpenAI function calling format (required for add).', + properties: { + function: { + type: 'object', + description: 'The function definition', + properties: { + description: { + type: 'string', + description: 'What the function does', }, - "name": { - "type": "string", - "description": "The function name (camelCase)" + name: { + type: 'string', + description: 'The function name (camelCase)', }, - "parameters": { - "type": "object", - "description": "The function parameters schema", - "properties": { - "properties": { - "type": "object", - "description": "Parameter definitions as key-value pairs" + parameters: { + type: 'object', + description: 'The function parameters schema', + properties: { + properties: { + type: 'object', + description: 'Parameter definitions as key-value pairs', }, - "required": { - "type": "array", - "description": "Array of required parameter names", - "items": { - "type": "string" - } + required: { + type: 'array', + description: 'Array of required parameter names', + items: { + type: 'string', + }, + }, + type: { + type: 'string', + description: "Must be 'object'", }, - "type": { - "type": "string", - "description": "Must be 'object'" - } }, - "required": [ - "type", - "properties" - ] - } + required: ['type', 'properties'], + }, }, - "required": [ - "name", - "parameters" - ] + required: ['name', 'parameters'], + }, + type: { + type: 'string', + description: "Must be 'function'", }, - "type": { - "type": "string", - "description": "Must be 'function'" - } }, - "required": [ - "type", - "function" - ] + required: ['type', 'function'], + }, + toolId: { + type: 'string', + description: + "The ID of the custom tool (required for edit/delete). Must be the exact toolId from the get_workflow_data custom tool response - do not guess or construct it. DO NOT PROVIDE THE TOOL ID IF THE OPERATION IS 'ADD'.", }, - "toolId": { - "type": "string", - "description": "The ID of the custom tool (required for edit/delete). Must be the exact toolId from the get_workflow_data custom tool response - do not guess or construct it. DO NOT PROVIDE THE TOOL ID IF THE OPERATION IS 'ADD'." - } }, - "required": [ - "operation" - ] + required: ['operation'], }, resultSchema: undefined, }, - ["manage_job"]: { + manage_job: { parameters: { - "type": "object", - "properties": { - "args": { - "type": "object", - "description": "Operation-specific arguments. For create: {title, prompt, cron?, time?, timezone?, lifecycle?, successCondition?, maxRuns?}. For get/delete: {jobId}. For update: {jobId, title?, prompt?, cron?, timezone?, status?, lifecycle?, successCondition?, maxRuns?}. For list: no args needed.", - "properties": { - "cron": { - "type": "string", - "description": "Cron expression for recurring jobs" + type: 'object', + properties: { + args: { + type: 'object', + description: + 'Operation-specific arguments. For create: {title, prompt, cron?, time?, timezone?, lifecycle?, successCondition?, maxRuns?}. For get/delete: {jobId}. For update: {jobId, title?, prompt?, cron?, timezone?, status?, lifecycle?, successCondition?, maxRuns?}. For list: no args needed.', + properties: { + cron: { + type: 'string', + description: 'Cron expression for recurring jobs', }, - "jobId": { - "type": "string", - "description": "Job ID (required for get, update, delete)" + jobId: { + type: 'string', + description: 'Job ID (required for get, update, delete)', }, - "lifecycle": { - "type": "string", - "description": "'persistent' (default) or 'until_complete'. Until_complete jobs stop when complete_job is called." + lifecycle: { + type: 'string', + description: + "'persistent' (default) or 'until_complete'. Until_complete jobs stop when complete_job is called.", }, - "maxRuns": { - "type": "integer", - "description": "Max executions before auto-completing. Safety limit." + maxRuns: { + type: 'integer', + description: 'Max executions before auto-completing. Safety limit.', }, - "prompt": { - "type": "string", - "description": "The prompt to execute when the job fires" + prompt: { + type: 'string', + description: 'The prompt to execute when the job fires', }, - "status": { - "type": "string", - "description": "Job status: active, paused" + status: { + type: 'string', + description: 'Job status: active, paused', }, - "successCondition": { - "type": "string", - "description": "What must happen for the job to be considered complete (until_complete lifecycle)." + successCondition: { + type: 'string', + description: + 'What must happen for the job to be considered complete (until_complete lifecycle).', }, - "time": { - "type": "string", - "description": "ISO 8601 datetime for one-time jobs or cron start time" + time: { + type: 'string', + description: 'ISO 8601 datetime for one-time jobs or cron start time', }, - "timezone": { - "type": "string", - "description": "IANA timezone (e.g. America/New_York). Defaults to UTC." + timezone: { + type: 'string', + description: 'IANA timezone (e.g. America/New_York). Defaults to UTC.', }, - "title": { - "type": "string", - "description": "Short descriptive title for the job (e.g. 'Email Poller')" - } - } + title: { + type: 'string', + description: "Short descriptive title for the job (e.g. 'Email Poller')", + }, + }, + }, + operation: { + type: 'string', + description: 'The operation to perform: create, list, get, update, delete', + enum: ['create', 'list', 'get', 'update', 'delete'], }, - "operation": { - "type": "string", - "description": "The operation to perform: create, list, get, update, delete", - "enum": [ - "create", - "list", - "get", - "update", - "delete" - ] - } }, - "required": [ - "operation" - ] + required: ['operation'], }, resultSchema: undefined, }, - ["manage_mcp_tool"]: { + manage_mcp_tool: { parameters: { - "type": "object", - "properties": { - "config": { - "type": "object", - "description": "Required for add and edit. The MCP server configuration.", - "properties": { - "enabled": { - "type": "boolean", - "description": "Whether the server is enabled (default: true)" + type: 'object', + properties: { + config: { + type: 'object', + description: 'Required for add and edit. The MCP server configuration.', + properties: { + enabled: { + type: 'boolean', + description: 'Whether the server is enabled (default: true)', }, - "headers": { - "type": "object", - "description": "Optional HTTP headers to send with requests (key-value pairs)" + headers: { + type: 'object', + description: 'Optional HTTP headers to send with requests (key-value pairs)', }, - "name": { - "type": "string", - "description": "Display name for the MCP server" + name: { + type: 'string', + description: 'Display name for the MCP server', }, - "timeout": { - "type": "number", - "description": "Request timeout in milliseconds (default: 30000)" + timeout: { + type: 'number', + description: 'Request timeout in milliseconds (default: 30000)', }, - "transport": { - "type": "string", - "description": "Transport protocol: 'streamable-http' or 'sse'", - "enum": [ - "streamable-http", - "sse" - ], - "default": "streamable-http" + transport: { + type: 'string', + description: "Transport protocol: 'streamable-http' or 'sse'", + enum: ['streamable-http', 'sse'], + default: 'streamable-http', }, - "url": { - "type": "string", - "description": "The MCP server endpoint URL (required for add)" - } - } + url: { + type: 'string', + description: 'The MCP server endpoint URL (required for add)', + }, + }, }, - "operation": { - "type": "string", - "description": "The operation to perform: 'add', 'edit', 'list', or 'delete'", - "enum": [ - "add", - "edit", - "delete", - "list" - ] + operation: { + type: 'string', + description: "The operation to perform: 'add', 'edit', 'list', or 'delete'", + enum: ['add', 'edit', 'delete', 'list'], + }, + serverId: { + type: 'string', + description: + "Required for edit and delete. The database ID of the MCP server. DO NOT PROVIDE if operation is 'add' or 'list'.", }, - "serverId": { - "type": "string", - "description": "Required for edit and delete. The database ID of the MCP server. DO NOT PROVIDE if operation is 'add' or 'list'." - } }, - "required": [ - "operation" - ] + required: ['operation'], }, resultSchema: undefined, }, - ["manage_skill"]: { + manage_skill: { parameters: { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Markdown instructions for the skill. Required for add, optional for edit." + type: 'object', + properties: { + content: { + type: 'string', + description: 'Markdown instructions for the skill. Required for add, optional for edit.', }, - "description": { - "type": "string", - "description": "Short description of the skill. Required for add, optional for edit." + description: { + type: 'string', + description: 'Short description of the skill. Required for add, optional for edit.', }, - "name": { - "type": "string", - "description": "Skill name in kebab-case (e.g. 'my-skill'). Required for add, optional for edit." + name: { + type: 'string', + description: + "Skill name in kebab-case (e.g. 'my-skill'). Required for add, optional for edit.", }, - "operation": { - "type": "string", - "description": "The operation to perform: 'add', 'edit', 'list', or 'delete'", - "enum": [ - "add", - "edit", - "delete", - "list" - ] + operation: { + type: 'string', + description: "The operation to perform: 'add', 'edit', 'list', or 'delete'", + enum: ['add', 'edit', 'delete', 'list'], + }, + skillId: { + type: 'string', + description: + "The ID of the skill (required for edit/delete). Must be the exact ID from the VFS or list. DO NOT PROVIDE if operation is 'add' or 'list'.", }, - "skillId": { - "type": "string", - "description": "The ID of the skill (required for edit/delete). Must be the exact ID from the VFS or list. DO NOT PROVIDE if operation is 'add' or 'list'." - } }, - "required": [ - "operation" - ] + required: ['operation'], }, resultSchema: undefined, }, - ["materialize_file"]: { + materialize_file: { parameters: { - "type": "object", - "properties": { - "fileName": { - "type": "string", - "description": "The name of the uploaded file to materialize (e.g. \"report.pdf\")" + type: 'object', + properties: { + fileName: { + type: 'string', + description: 'The name of the uploaded file to materialize (e.g. "report.pdf")', }, - "knowledgeBaseId": { - "type": "string", - "description": "ID of an existing knowledge base to add the file to (only used with operation \"knowledge_base\"). If omitted, a new KB is created." + knowledgeBaseId: { + type: 'string', + description: + 'ID of an existing knowledge base to add the file to (only used with operation "knowledge_base"). If omitted, a new KB is created.', }, - "operation": { - "type": "string", - "description": "What to do with the file. \"save\" promotes it to files/. \"import\" imports a workflow JSON. \"table\" converts CSV/TSV/JSON to a table. \"knowledge_base\" saves and adds to a KB. Defaults to \"save\".", - "enum": [ - "save", - "import", - "table", - "knowledge_base" + operation: { + type: 'string', + description: + 'What to do with the file. "save" promotes it to files/. "import" imports a workflow JSON. "table" converts CSV/TSV/JSON to a table. "knowledge_base" saves and adds to a KB. Defaults to "save".', + enum: ['save', 'import', 'table', 'knowledge_base'], + default: 'save', + }, + tableName: { + type: 'string', + description: + 'Custom name for the table (only used with operation "table"). Defaults to the file name without extension.', + }, + }, + required: ['fileName'], + }, + resultSchema: undefined, + }, + move_folder: { + parameters: { + type: 'object', + properties: { + folderId: { + type: 'string', + description: 'The folder ID to move.', + }, + parentId: { + type: 'string', + description: + 'Target parent folder ID. Omit or pass empty string to move to workspace root.', + }, + }, + required: ['folderId'], + }, + resultSchema: undefined, + }, + move_workflow: { + parameters: { + type: 'object', + properties: { + folderId: { + type: 'string', + description: 'Target folder ID. Omit or pass empty string to move to workspace root.', + }, + workflowId: { + type: 'string', + description: 'The workflow ID to move.', + }, + }, + required: ['workflowId'], + }, + resultSchema: undefined, + }, + oauth_get_auth_link: { + parameters: { + type: 'object', + properties: { + providerName: { + type: 'string', + description: + "The name of the OAuth provider to connect (e.g., 'Slack', 'Gmail', 'Google Calendar', 'GitHub')", + }, + }, + required: ['providerName'], + }, + resultSchema: undefined, + }, + oauth_request_access: { + parameters: { + type: 'object', + properties: { + providerName: { + type: 'string', + description: + "The name of the OAuth provider to connect (e.g., 'Slack', 'Gmail', 'Google Calendar')", + }, + }, + required: ['providerName'], + }, + resultSchema: undefined, + }, + open_resource: { + parameters: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'The resource ID to open.', + }, + type: { + type: 'string', + description: 'The resource type to open.', + enum: ['workflow', 'table', 'knowledgebase', 'file'], + }, + }, + required: ['type', 'id'], + }, + resultSchema: undefined, + }, + read: { + parameters: { + type: 'object', + properties: { + limit: { + type: 'number', + description: 'Maximum number of lines to read.', + }, + offset: { + type: 'number', + description: 'Line offset to start reading from (0-indexed).', + }, + outputTable: { + type: 'string', + description: + 'Table ID to import the file contents into (CSV/JSON). All existing rows are replaced. Example: "tbl_abc123"', + }, + path: { + type: 'string', + description: "Path to the file to read (e.g. 'workflows/My Workflow/state.json').", + }, + }, + required: ['path'], + }, + resultSchema: undefined, + }, + redeploy: { + parameters: { + type: 'object', + properties: { + workflowId: { + type: 'string', + description: 'Workflow ID to redeploy (required in workspace context)', + }, + }, + }, + resultSchema: undefined, + }, + rename_file: { + parameters: { + type: 'object', + properties: { + fileId: { + type: 'string', + description: 'Canonical workspace file ID of the file to rename.', + }, + newName: { + type: 'string', + description: + 'New filename including extension, e.g. "draft_v2.md". Must not contain slashes.', + }, + }, + required: ['fileId', 'newName'], + }, + resultSchema: { + type: 'object', + properties: { + data: { + type: 'object', + description: 'Contains id and the new name.', + }, + message: { + type: 'string', + description: 'Human-readable outcome.', + }, + success: { + type: 'boolean', + description: 'Whether the rename succeeded.', + }, + }, + required: ['success', 'message'], + }, + }, + rename_workflow: { + parameters: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'The new name for the workflow.', + }, + workflowId: { + type: 'string', + description: 'The workflow ID to rename.', + }, + }, + required: ['workflowId', 'name'], + }, + resultSchema: undefined, + }, + research: { + parameters: { + properties: { + topic: { + description: 'The topic to research.', + type: 'string', + }, + }, + required: ['topic'], + type: 'object', + }, + resultSchema: undefined, + }, + respond: { + parameters: { + additionalProperties: true, + properties: { + output: { + description: + 'The result — facts, status, VFS paths to persisted data, whatever the caller needs to act on.', + type: 'string', + }, + success: { + description: 'Whether the task completed successfully', + type: 'boolean', + }, + type: { + description: 'Optional logical result type override', + type: 'string', + }, + }, + required: ['output', 'success'], + type: 'object', + }, + resultSchema: undefined, + }, + revert_to_version: { + parameters: { + type: 'object', + properties: { + version: { + type: 'number', + description: 'The deployment version number to revert to', + }, + workflowId: { + type: 'string', + description: 'The workflow ID', + }, + }, + required: ['workflowId', 'version'], + }, + resultSchema: undefined, + }, + run: { + parameters: { + properties: { + context: { + description: 'Pre-gathered context: workflow state, block IDs, input requirements.', + type: 'string', + }, + request: { + description: 'What to run or what logs to check.', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + resultSchema: undefined, + }, + run_block: { + parameters: { + type: 'object', + properties: { + blockId: { + type: 'string', + description: 'The block ID to run in isolation.', + }, + executionId: { + type: 'string', + description: + 'Optional execution ID to load the snapshot from. Uses latest execution if omitted.', + }, + useDeployedState: { + type: 'boolean', + description: + 'When true, runs the deployed version instead of the live draft. Default: false (draft).', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to run. If not provided, uses the current workflow in context.', + }, + workflow_input: { + type: 'object', + description: 'JSON object with key-value mappings where each key is an input field name', + }, + }, + required: ['blockId'], + }, + resultSchema: undefined, + }, + run_from_block: { + parameters: { + type: 'object', + properties: { + executionId: { + type: 'string', + description: + 'Optional execution ID to load the snapshot from. Uses latest execution if omitted.', + }, + startBlockId: { + type: 'string', + description: 'The block ID to start execution from.', + }, + useDeployedState: { + type: 'boolean', + description: + 'When true, runs the deployed version instead of the live draft. Default: false (draft).', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to run. If not provided, uses the current workflow in context.', + }, + workflow_input: { + type: 'object', + description: 'JSON object with key-value mappings where each key is an input field name', + }, + }, + required: ['startBlockId'], + }, + resultSchema: undefined, + }, + run_workflow: { + parameters: { + type: 'object', + properties: { + useDeployedState: { + type: 'boolean', + description: + 'When true, runs the deployed version instead of the live draft. Default: false (draft).', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to run. If not provided, uses the current workflow in context.', + }, + workflow_input: { + type: 'object', + description: 'JSON object with key-value mappings where each key is an input field name', + }, + }, + required: ['workflow_input'], + }, + resultSchema: undefined, + }, + run_workflow_until_block: { + parameters: { + type: 'object', + properties: { + stopAfterBlockId: { + type: 'string', + description: 'The block ID to stop after. Execution halts once this block completes.', + }, + useDeployedState: { + type: 'boolean', + description: + 'When true, runs the deployed version instead of the live draft. Default: false (draft).', + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID to run. If not provided, uses the current workflow in context.', + }, + workflow_input: { + type: 'object', + description: 'JSON object with key-value mappings where each key is an input field name', + }, + }, + required: ['stopAfterBlockId'], + }, + resultSchema: undefined, + }, + scrape_page: { + parameters: { + type: 'object', + properties: { + include_links: { + type: 'boolean', + description: 'Extract all links from the page (default false)', + }, + url: { + type: 'string', + description: 'The URL to scrape (must include https://)', + }, + wait_for: { + type: 'string', + description: 'CSS selector to wait for before scraping (for JS-heavy pages)', + }, + }, + required: ['url'], + }, + resultSchema: undefined, + }, + search_documentation: { + parameters: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'The search query', + }, + topK: { + type: 'number', + description: 'Number of results (max 10)', + }, + }, + required: ['query'], + }, + resultSchema: undefined, + }, + search_library_docs: { + parameters: { + type: 'object', + properties: { + library_name: { + type: 'string', + description: "Name of the library to search for (e.g., 'nextjs', 'stripe', 'langchain')", + }, + query: { + type: 'string', + description: 'The question or topic to find documentation for - be specific', + }, + version: { + type: 'string', + description: "Specific version (optional, e.g., '14', 'v2')", + }, + }, + required: ['library_name', 'query'], + }, + resultSchema: undefined, + }, + search_online: { + parameters: { + type: 'object', + properties: { + category: { + type: 'string', + description: 'Filter by category', + enum: [ + 'news', + 'tweet', + 'github', + 'paper', + 'company', + 'research paper', + 'linkedin profile', + 'pdf', + 'personal site', ], - "default": "save" }, - "tableName": { - "type": "string", - "description": "Custom name for the table (only used with operation \"table\"). Defaults to the file name without extension." - } + 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', + }, }, - "required": [ - "fileName" - ] + required: ['query'], }, resultSchema: undefined, }, - ["move_folder"]: { + search_patterns: { parameters: { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "description": "The folder ID to move." + type: 'object', + properties: { + limit: { + type: 'integer', + description: 'Maximum number of unique pattern examples to return (defaults to 3).', }, - "parentId": { - "type": "string", - "description": "Target parent folder ID. Omit or pass empty string to move to workspace root." - } - }, - "required": [ - "folderId" - ] - }, - resultSchema: undefined, - }, - ["move_workflow"]: { - parameters: { - "type": "object", - "properties": { - "folderId": { - "type": "string", - "description": "Target folder ID. Omit or pass empty string to move to workspace root." - }, - "workflowId": { - "type": "string", - "description": "The workflow ID to move." - } - }, - "required": [ - "workflowId" - ] - }, - resultSchema: undefined, - }, - ["oauth_get_auth_link"]: { - parameters: { - "type": "object", - "properties": { - "providerName": { - "type": "string", - "description": "The name of the OAuth provider to connect (e.g., 'Slack', 'Gmail', 'Google Calendar', 'GitHub')" - } - }, - "required": [ - "providerName" - ] - }, - resultSchema: undefined, - }, - ["oauth_request_access"]: { - parameters: { - "type": "object", - "properties": { - "providerName": { - "type": "string", - "description": "The name of the OAuth provider to connect (e.g., 'Slack', 'Gmail', 'Google Calendar')" - } - }, - "required": [ - "providerName" - ] - }, - resultSchema: undefined, - }, - ["open_resource"]: { - parameters: { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The resource ID to open." - }, - "type": { - "type": "string", - "description": "The resource type to open.", - "enum": [ - "workflow", - "table", - "knowledgebase", - "file" - ] - } - }, - "required": [ - "type", - "id" - ] - }, - resultSchema: undefined, - }, - ["read"]: { - parameters: { - "type": "object", - "properties": { - "limit": { - "type": "number", - "description": "Maximum number of lines to read." - }, - "offset": { - "type": "number", - "description": "Line offset to start reading from (0-indexed)." - }, - "outputTable": { - "type": "string", - "description": "Table ID to import the file contents into (CSV/JSON). All existing rows are replaced. Example: \"tbl_abc123\"" - }, - "path": { - "type": "string", - "description": "Path to the file to read (e.g. 'workflows/My Workflow/state.json')." - } - }, - "required": [ - "path" - ] - }, - resultSchema: undefined, - }, - ["redeploy"]: { - parameters: { - "type": "object", - "properties": { - "workflowId": { - "type": "string", - "description": "Workflow ID to redeploy (required in workspace context)" - } - } - }, - resultSchema: undefined, - }, - ["rename_file"]: { - parameters: { - "type": "object", - "properties": { - "fileId": { - "type": "string", - "description": "Canonical workspace file ID of the file to rename." - }, - "newName": { - "type": "string", - "description": "New filename including extension, e.g. \"draft_v2.md\". Must not contain slashes." - } - }, - "required": [ - "fileId", - "newName" - ] - }, - resultSchema: { - "type": "object", - "properties": { - "data": { - "type": "object", - "description": "Contains id and the new name." - }, - "message": { - "type": "string", - "description": "Human-readable outcome." - }, - "success": { - "type": "boolean", - "description": "Whether the rename succeeded." - } - }, - "required": [ - "success", - "message" - ] - }, - }, - ["rename_workflow"]: { - parameters: { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The new name for the workflow." - }, - "workflowId": { - "type": "string", - "description": "The workflow ID to rename." - } - }, - "required": [ - "workflowId", - "name" - ] - }, - resultSchema: undefined, - }, - ["research"]: { - parameters: { - "properties": { - "topic": { - "description": "The topic to research.", - "type": "string" - } - }, - "required": [ - "topic" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["respond"]: { - parameters: { - "additionalProperties": true, - "properties": { - "output": { - "description": "The result — facts, status, VFS paths to persisted data, whatever the caller needs to act on.", - "type": "string" - }, - "success": { - "description": "Whether the task completed successfully", - "type": "boolean" - }, - "type": { - "description": "Optional logical result type override", - "type": "string" - } - }, - "required": [ - "output", - "success" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["revert_to_version"]: { - parameters: { - "type": "object", - "properties": { - "version": { - "type": "number", - "description": "The deployment version number to revert to" - }, - "workflowId": { - "type": "string", - "description": "The workflow ID" - } - }, - "required": [ - "workflowId", - "version" - ] - }, - resultSchema: undefined, - }, - ["run"]: { - parameters: { - "properties": { - "context": { - "description": "Pre-gathered context: workflow state, block IDs, input requirements.", - "type": "string" - }, - "request": { - "description": "What to run or what logs to check.", - "type": "string" - } - }, - "required": [ - "request" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["run_block"]: { - parameters: { - "type": "object", - "properties": { - "blockId": { - "type": "string", - "description": "The block ID to run in isolation." - }, - "executionId": { - "type": "string", - "description": "Optional execution ID to load the snapshot from. Uses latest execution if omitted." - }, - "useDeployedState": { - "type": "boolean", - "description": "When true, runs the deployed version instead of the live draft. Default: false (draft)." - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID to run. If not provided, uses the current workflow in context." - }, - "workflow_input": { - "type": "object", - "description": "JSON object with key-value mappings where each key is an input field name" - } - }, - "required": [ - "blockId" - ] - }, - resultSchema: undefined, - }, - ["run_from_block"]: { - parameters: { - "type": "object", - "properties": { - "executionId": { - "type": "string", - "description": "Optional execution ID to load the snapshot from. Uses latest execution if omitted." - }, - "startBlockId": { - "type": "string", - "description": "The block ID to start execution from." - }, - "useDeployedState": { - "type": "boolean", - "description": "When true, runs the deployed version instead of the live draft. Default: false (draft)." - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID to run. If not provided, uses the current workflow in context." - }, - "workflow_input": { - "type": "object", - "description": "JSON object with key-value mappings where each key is an input field name" - } - }, - "required": [ - "startBlockId" - ] - }, - resultSchema: undefined, - }, - ["run_workflow"]: { - parameters: { - "type": "object", - "properties": { - "useDeployedState": { - "type": "boolean", - "description": "When true, runs the deployed version instead of the live draft. Default: false (draft)." - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID to run. If not provided, uses the current workflow in context." - }, - "workflow_input": { - "type": "object", - "description": "JSON object with key-value mappings where each key is an input field name" - } - }, - "required": [ - "workflow_input" - ] - }, - resultSchema: undefined, - }, - ["run_workflow_until_block"]: { - parameters: { - "type": "object", - "properties": { - "stopAfterBlockId": { - "type": "string", - "description": "The block ID to stop after. Execution halts once this block completes." - }, - "useDeployedState": { - "type": "boolean", - "description": "When true, runs the deployed version instead of the live draft. Default: false (draft)." - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID to run. If not provided, uses the current workflow in context." - }, - "workflow_input": { - "type": "object", - "description": "JSON object with key-value mappings where each key is an input field name" - } - }, - "required": [ - "stopAfterBlockId" - ] - }, - resultSchema: undefined, - }, - ["scrape_page"]: { - parameters: { - "type": "object", - "properties": { - "include_links": { - "type": "boolean", - "description": "Extract all links from the page (default false)" - }, - "url": { - "type": "string", - "description": "The URL to scrape (must include https://)" - }, - "wait_for": { - "type": "string", - "description": "CSS selector to wait for before scraping (for JS-heavy pages)" - } - }, - "required": [ - "url" - ] - }, - resultSchema: undefined, - }, - ["search_documentation"]: { - parameters: { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "The search query" - }, - "topK": { - "type": "number", - "description": "Number of results (max 10)" - } - }, - "required": [ - "query" - ] - }, - resultSchema: undefined, - }, - ["search_library_docs"]: { - parameters: { - "type": "object", - "properties": { - "library_name": { - "type": "string", - "description": "Name of the library to search for (e.g., 'nextjs', 'stripe', 'langchain')" - }, - "query": { - "type": "string", - "description": "The question or topic to find documentation for - be specific" - }, - "version": { - "type": "string", - "description": "Specific version (optional, e.g., '14', 'v2')" - } - }, - "required": [ - "library_name", - "query" - ] - }, - resultSchema: undefined, - }, - ["search_online"]: { - parameters: { - "type": "object", - "properties": { - "category": { - "type": "string", - "description": "Filter by category", - "enum": [ - "news", - "tweet", - "github", - "paper", - "company", - "research paper", - "linkedin profile", - "pdf", - "personal site" - ] - }, - "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" - } - }, - "required": [ - "query" - ] - }, - resultSchema: undefined, - }, - ["search_patterns"]: { - parameters: { - "type": "object", - "properties": { - "limit": { - "type": "integer", - "description": "Maximum number of unique pattern examples to return (defaults to 3)." - }, - "queries": { - "type": "array", - "description": "Up to 3 descriptive strings explaining the workflow pattern(s) you need. Focus on intent and desired outcomes.", - "items": { - "type": "string", - "description": "Example: \"how to automate wealthbox meeting notes into follow-up tasks\"" - } - } - }, - "required": [ - "queries" - ] - }, - resultSchema: undefined, - }, - ["set_environment_variables"]: { - parameters: { - "type": "object", - "properties": { - "variables": { - "type": "array", - "description": "List of env vars to set", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Variable name" - }, - "value": { - "type": "string", - "description": "Variable value" - } - }, - "required": [ - "name", - "value" - ] - } - } - }, - "required": [ - "variables" - ] - }, - resultSchema: undefined, - }, - ["set_global_workflow_variables"]: { - parameters: { - "type": "object", - "properties": { - "operations": { - "type": "array", - "description": "List of operations to apply", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "operation": { - "type": "string", - "enum": [ - "add", - "delete", - "edit" - ] - }, - "type": { - "type": "string", - "enum": [ - "plain", - "number", - "boolean", - "array", - "object" - ] - }, - "value": { - "type": "string" - } - }, - "required": [ - "operation", - "name", - "type", - "value" - ] - } - }, - "workflowId": { - "type": "string", - "description": "Optional workflow ID. If not provided, uses the current workflow in context." - } - }, - "required": [ - "operations" - ] - }, - resultSchema: undefined, - }, - ["superagent"]: { - parameters: { - "properties": { - "task": { - "description": "A single sentence — the agent has full conversation context. Do NOT pre-read credentials or look up configs. Example: 'send the email we discussed' or 'check my calendar for tomorrow'.", - "type": "string" - } - }, - "required": [ - "task" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["table"]: { - parameters: { - "properties": { - "request": { - "description": "What table action is needed.", - "type": "string" - } - }, - "required": [ - "request" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["tool_search_tool_regex"]: { - parameters: { - "properties": { - "case_insensitive": { - "description": "Whether the regex should be case-insensitive (default true).", - "type": "boolean" - }, - "max_results": { - "description": "Maximum number of tools to return (optional).", - "type": "integer" - }, - "pattern": { - "description": "Regular expression to match tool names or descriptions.", - "type": "string" - } - }, - "required": [ - "pattern" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["update_job_history"]: { - parameters: { - "type": "object", - "properties": { - "jobId": { - "type": "string", - "description": "The job ID." - }, - "summary": { - "type": "string", - "description": "A concise summary of what was done this run (e.g., 'Sent follow-up emails to 3 leads: Alice, Bob, Carol')." - } - }, - "required": [ - "jobId", - "summary" - ] - }, - resultSchema: undefined, - }, - ["update_workspace_mcp_server"]: { - parameters: { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "New description for the server" - }, - "isPublic": { - "type": "boolean", - "description": "Whether the server is publicly accessible" - }, - "name": { - "type": "string", - "description": "New name for the server" - }, - "serverId": { - "type": "string", - "description": "Required: the MCP server ID to update" - } - }, - "required": [ - "serverId" - ] - }, - resultSchema: undefined, - }, - ["user_memory"]: { - parameters: { - "type": "object", - "properties": { - "confidence": { - "type": "number", - "description": "Confidence level 0-1 (default 1.0 for explicit, 0.8 for inferred)" - }, - "correct_value": { - "type": "string", - "description": "The correct value to replace the wrong one (for 'correct' operation)" - }, - "key": { - "type": "string", - "description": "Unique key for the memory (e.g., 'preferred_model', 'slack_credential')" - }, - "limit": { - "type": "number", - "description": "Number of results for search (default 10)" - }, - "memory_type": { - "type": "string", - "description": "Type of memory: 'preference', 'entity', 'history', or 'correction'", - "enum": [ - "preference", - "entity", - "history", - "correction" - ] - }, - "operation": { - "type": "string", - "description": "Operation: 'add', 'search', 'delete', 'correct', or 'list'", - "enum": [ - "add", - "search", - "delete", - "correct", - "list" - ] - }, - "query": { - "type": "string", - "description": "Search query to find relevant memories" - }, - "source": { - "type": "string", - "description": "Source: 'explicit' (user told you) or 'inferred' (you observed)", - "enum": [ - "explicit", - "inferred" - ] - }, - "value": { - "type": "string", - "description": "Value to remember" - } - }, - "required": [ - "operation" - ] - }, - resultSchema: undefined, - }, - ["user_table"]: { - parameters: { - "type": "object", - "properties": { - "args": { - "type": "object", - "description": "Arguments for the operation", - "properties": { - "column": { - "type": "object", - "description": "Column definition for add_column: { name, type, unique?, position? }" - }, - "columnName": { - "type": "string", - "description": "Column name (required for rename_column, update_column; use columnNames array for batch delete_column)" - }, - "columnNames": { - "type": "array", - "description": "Array of column names to delete at once (for delete_column). Preferred over columnName when deleting multiple columns." - }, - "data": { - "type": "object", - "description": "Row data as key-value pairs (required for insert_row, update_row)" - }, - "description": { - "type": "string", - "description": "Table description (optional for 'create')" - }, - "fileId": { - "type": "string", - "description": "Canonical workspace file ID for create_from_file/import_file. Discover via read(\"files/{name}/meta.json\") or glob(\"files/by-id/*/meta.json\")." - }, - "filePath": { - "type": "string", - "description": "Legacy workspace file reference for create_from_file/import_file. Prefer fileId." - }, - "filter": { - "type": "object", - "description": "MongoDB-style filter for query_rows, update_rows_by_filter, delete_rows_by_filter" - }, - "limit": { - "type": "number", - "description": "Maximum rows to return or affect (optional, default 100)" - }, - "name": { - "type": "string", - "description": "Table name (required for 'create')" - }, - "newName": { - "type": "string", - "description": "New column name (required for rename_column)" - }, - "newType": { - "type": "string", - "description": "New column type (optional for update_column). Types: string, number, boolean, date, json" - }, - "offset": { - "type": "number", - "description": "Number of rows to skip (optional for query_rows, default 0)" - }, - "outputFormat": { - "type": "string", - "description": "Explicit format override for outputPath. Usually unnecessary — the file extension determines the format automatically. Only use this to force a different format than what the extension implies.", - "enum": [ - "json", - "csv", - "txt", - "md", - "html" - ] - }, - "outputPath": { - "type": "string", - "description": "Pipe query_rows results directly to a NEW workspace file. The format is auto-inferred from the file extension: .csv → CSV, .json → JSON, .md → Markdown, etc. Use .csv for tabular exports. Use a flat path like \"files/export.csv\" — nested paths are not supported." - }, - "rowId": { - "type": "string", - "description": "Row ID (required for get_row, update_row, delete_row)" - }, - "rowIds": { - "type": "array", - "description": "Array of row IDs to delete (for batch_delete_rows)" - }, - "rows": { - "type": "array", - "description": "Array of row data objects (required for batch_insert_rows)" - }, - "schema": { - "type": "object", - "description": "Table schema with columns array (required for 'create'). Each column: { name, type, unique? }" - }, - "sort": { - "type": "object", - "description": "Sort specification as { field: 'asc' | 'desc' } (optional for query_rows)" - }, - "tableId": { - "type": "string", - "description": "Table ID (required for most operations except 'create')" - }, - "unique": { - "type": "boolean", - "description": "Set column unique constraint (optional for update_column)" - }, - "updates": { - "type": "array", - "description": "Array of per-row updates: [{ rowId, data: { col: val } }] (for batch_update_rows)" - }, - "values": { - "type": "object", - "description": "Map of rowId to value for single-column batch update: { \"rowId1\": val1, \"rowId2\": val2 } (for batch_update_rows with columnName)" - } - } - }, - "operation": { - "type": "string", - "description": "The operation to perform", - "enum": [ - "create", - "create_from_file", - "import_file", - "get", - "get_schema", - "delete", - "insert_row", - "batch_insert_rows", - "get_row", - "query_rows", - "update_row", - "delete_row", - "update_rows_by_filter", - "delete_rows_by_filter", - "batch_update_rows", - "batch_delete_rows", - "add_column", - "rename_column", - "delete_column", - "update_column" - ] - } - }, - "required": [ - "operation", - "args" - ] - }, - resultSchema: { - "type": "object", - "properties": { - "data": { - "type": "object", - "description": "Operation-specific result payload." - }, - "message": { - "type": "string", - "description": "Human-readable outcome summary." - }, - "success": { - "type": "boolean", - "description": "Whether the operation succeeded." - } - }, - "required": [ - "success", - "message" - ] - }, - }, - ["workflow"]: { - parameters: { - "properties": { - "request": { - "description": "A single sentence — the agent has full conversation context and VFS access. Do NOT look up IDs or pre-read data; the workflow agent does its own research. Example: 'move all the return letter workflows into a folder called Letters'.", - "type": "string" - } - }, - "required": [ - "request" - ], - "type": "object" - }, - resultSchema: undefined, - }, - ["workspace_file"]: { - parameters: { - "type": "object", - "properties": { - "operation": { - "type": "string", - "description": "The file operation to perform.", - "enum": [ - "append", - "update", - "patch", - "rename", - "delete" - ] - }, - "target": { - "type": "object", - "description": "Explicit file target. Use kind=file_id + fileId for all operations. Emit target keys in this order: kind, fileId, fileName.", - "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." - }, - "fileName": { - "type": "string", - "description": "Plain workspace filename including extension, e.g. \"main.py\" or \"report.docx\". Required when target.kind=new_file." - } + queries: { + type: 'array', + description: + 'Up to 3 descriptive strings explaining the workflow pattern(s) you need. Focus on intent and desired outcomes.', + items: { + type: 'string', + description: 'Example: "how to automate wealthbox meeting notes into follow-up tasks"', }, - "required": [ - "kind" - ] }, - "title": { - "type": "string", - "description": "Optional short UI label for create/append chunks, e.g. \"Chapter 1\" or \"Slide 3\"." - }, - "contentType": { - "type": "string", - "description": "Optional MIME type override. Usually omit and let the system infer from the target file extension.", - "enum": [ - "text/markdown", - "text/html", - "text/plain", - "application/json", - "text/csv", - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/pdf" - ] - }, - "edit": { - "type": "object", - "description": "Patch metadata. Use strategy=search_replace for exact text replacement, or strategy=anchored for line-based inserts/replacements/deletions. Emit edit keys in this order: strategy, search, replace, replaceAll, mode, occurrence, before_anchor, after_anchor, anchor, start_anchor, end_anchor, content.", - "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." - }, - "replace": { - "type": "string", - "description": "Replacement text when strategy=search_replace." - }, - "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": "Boundary line kept after inserted replacement content. Required for mode=replace_between." - }, - "anchor": { - "type": "string", - "description": "Anchor line after which new content is inserted. Required for mode=insert_after." - }, - "start_anchor": { - "type": "string", - "description": "First line to delete. Required for mode=delete_between." - }, - "end_anchor": { - "type": "string", - "description": "First line to keep after deletion. Required for mode=delete_between." - }, - "content": { - "type": "string", - "description": "Inserted or replacement content for anchored edits. Not used for delete_between." - } - } - }, - "newName": { - "type": "string", - "description": "New file name for rename. Must be a plain workspace filename like \"main.py\"." - }, - "content": { - "type": "string", - "description": "File content for append or update. For .pptx/.docx/.pdf this must be JavaScript source code for the corresponding generator runtime." - } }, - "required": [ - "operation", - "target" - ] + required: ['queries'], + }, + resultSchema: undefined, + }, + set_environment_variables: { + parameters: { + type: 'object', + properties: { + variables: { + type: 'array', + description: 'List of env vars to set', + items: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'Variable name', + }, + value: { + type: 'string', + description: 'Variable value', + }, + }, + required: ['name', 'value'], + }, + }, + }, + required: ['variables'], + }, + resultSchema: undefined, + }, + set_global_workflow_variables: { + parameters: { + type: 'object', + properties: { + operations: { + type: 'array', + description: 'List of operations to apply', + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + operation: { + type: 'string', + enum: ['add', 'delete', 'edit'], + }, + type: { + type: 'string', + enum: ['plain', 'number', 'boolean', 'array', 'object'], + }, + value: { + type: 'string', + }, + }, + required: ['operation', 'name', 'type', 'value'], + }, + }, + workflowId: { + type: 'string', + description: + 'Optional workflow ID. If not provided, uses the current workflow in context.', + }, + }, + required: ['operations'], + }, + resultSchema: undefined, + }, + superagent: { + parameters: { + properties: { + task: { + description: + "A single sentence — the agent has full conversation context. Do NOT pre-read credentials or look up configs. Example: 'send the email we discussed' or 'check my calendar for tomorrow'.", + type: 'string', + }, + }, + required: ['task'], + type: 'object', + }, + resultSchema: undefined, + }, + table: { + parameters: { + properties: { + request: { + description: 'What table action is needed.', + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + resultSchema: undefined, + }, + tool_search_tool_regex: { + parameters: { + properties: { + case_insensitive: { + description: 'Whether the regex should be case-insensitive (default true).', + type: 'boolean', + }, + max_results: { + description: 'Maximum number of tools to return (optional).', + type: 'integer', + }, + pattern: { + description: 'Regular expression to match tool names or descriptions.', + type: 'string', + }, + }, + required: ['pattern'], + type: 'object', + }, + resultSchema: undefined, + }, + update_job_history: { + parameters: { + type: 'object', + properties: { + jobId: { + type: 'string', + description: 'The job ID.', + }, + summary: { + type: 'string', + description: + "A concise summary of what was done this run (e.g., 'Sent follow-up emails to 3 leads: Alice, Bob, Carol').", + }, + }, + required: ['jobId', 'summary'], + }, + resultSchema: undefined, + }, + update_workspace_mcp_server: { + parameters: { + type: 'object', + properties: { + description: { + type: 'string', + description: 'New description for the server', + }, + isPublic: { + type: 'boolean', + description: 'Whether the server is publicly accessible', + }, + name: { + type: 'string', + description: 'New name for the server', + }, + serverId: { + type: 'string', + description: 'Required: the MCP server ID to update', + }, + }, + required: ['serverId'], + }, + resultSchema: undefined, + }, + user_memory: { + parameters: { + type: 'object', + properties: { + confidence: { + type: 'number', + description: 'Confidence level 0-1 (default 1.0 for explicit, 0.8 for inferred)', + }, + correct_value: { + type: 'string', + description: "The correct value to replace the wrong one (for 'correct' operation)", + }, + key: { + type: 'string', + description: "Unique key for the memory (e.g., 'preferred_model', 'slack_credential')", + }, + limit: { + type: 'number', + description: 'Number of results for search (default 10)', + }, + memory_type: { + type: 'string', + description: "Type of memory: 'preference', 'entity', 'history', or 'correction'", + enum: ['preference', 'entity', 'history', 'correction'], + }, + operation: { + type: 'string', + description: "Operation: 'add', 'search', 'delete', 'correct', or 'list'", + enum: ['add', 'search', 'delete', 'correct', 'list'], + }, + query: { + type: 'string', + description: 'Search query to find relevant memories', + }, + source: { + type: 'string', + description: "Source: 'explicit' (user told you) or 'inferred' (you observed)", + enum: ['explicit', 'inferred'], + }, + value: { + type: 'string', + description: 'Value to remember', + }, + }, + required: ['operation'], + }, + resultSchema: undefined, + }, + user_table: { + parameters: { + type: 'object', + properties: { + args: { + type: 'object', + description: 'Arguments for the operation', + properties: { + column: { + type: 'object', + description: 'Column definition for add_column: { name, type, unique?, position? }', + }, + columnName: { + type: 'string', + description: + 'Column name (required for rename_column, update_column; use columnNames array for batch delete_column)', + }, + columnNames: { + type: 'array', + description: + 'Array of column names to delete at once (for delete_column). Preferred over columnName when deleting multiple columns.', + }, + data: { + type: 'object', + description: 'Row data as key-value pairs (required for insert_row, update_row)', + }, + description: { + type: 'string', + description: "Table description (optional for 'create')", + }, + fileId: { + type: 'string', + description: + 'Canonical workspace file ID for create_from_file/import_file. Discover via read("files/{name}/meta.json") or glob("files/by-id/*/meta.json").', + }, + filePath: { + type: 'string', + description: + 'Legacy workspace file reference for create_from_file/import_file. Prefer fileId.', + }, + filter: { + type: 'object', + description: + 'MongoDB-style filter for query_rows, update_rows_by_filter, delete_rows_by_filter', + }, + limit: { + type: 'number', + description: 'Maximum rows to return or affect (optional, default 100)', + }, + name: { + type: 'string', + description: "Table name (required for 'create')", + }, + newName: { + type: 'string', + description: 'New column name (required for rename_column)', + }, + newType: { + type: 'string', + description: + 'New column type (optional for update_column). Types: string, number, boolean, date, json', + }, + offset: { + type: 'number', + description: 'Number of rows to skip (optional for query_rows, default 0)', + }, + outputFormat: { + type: 'string', + description: + 'Explicit format override for outputPath. Usually unnecessary — the file extension determines the format automatically. Only use this to force a different format than what the extension implies.', + enum: ['json', 'csv', 'txt', 'md', 'html'], + }, + outputPath: { + type: 'string', + description: + 'Pipe query_rows results directly to a NEW workspace file. The format is auto-inferred from the file extension: .csv → CSV, .json → JSON, .md → Markdown, etc. Use .csv for tabular exports. Use a flat path like "files/export.csv" — nested paths are not supported.', + }, + rowId: { + type: 'string', + description: 'Row ID (required for get_row, update_row, delete_row)', + }, + rowIds: { + type: 'array', + description: 'Array of row IDs to delete (for batch_delete_rows)', + }, + rows: { + type: 'array', + description: 'Array of row data objects (required for batch_insert_rows)', + }, + schema: { + type: 'object', + description: + "Table schema with columns array (required for 'create'). Each column: { name, type, unique? }", + }, + sort: { + type: 'object', + description: + "Sort specification as { field: 'asc' | 'desc' } (optional for query_rows)", + }, + tableId: { + type: 'string', + description: "Table ID (required for most operations except 'create')", + }, + unique: { + type: 'boolean', + description: 'Set column unique constraint (optional for update_column)', + }, + updates: { + type: 'array', + description: + 'Array of per-row updates: [{ rowId, data: { col: val } }] (for batch_update_rows)', + }, + values: { + type: 'object', + description: + 'Map of rowId to value for single-column batch update: { "rowId1": val1, "rowId2": val2 } (for batch_update_rows with columnName)', + }, + }, + }, + operation: { + type: 'string', + description: 'The operation to perform', + enum: [ + 'create', + 'create_from_file', + 'import_file', + 'get', + 'get_schema', + 'delete', + 'insert_row', + 'batch_insert_rows', + 'get_row', + 'query_rows', + 'update_row', + 'delete_row', + 'update_rows_by_filter', + 'delete_rows_by_filter', + 'batch_update_rows', + 'batch_delete_rows', + 'add_column', + 'rename_column', + 'delete_column', + 'update_column', + ], + }, + }, + required: ['operation', 'args'], }, resultSchema: { - "type": "object", - "properties": { - "data": { - "type": "object", - "description": "Optional operation metadata such as file id, file name, size, and content type." + type: 'object', + properties: { + data: { + type: 'object', + description: 'Operation-specific result payload.', }, - "message": { - "type": "string", - "description": "Human-readable summary of the outcome." + message: { + type: 'string', + description: 'Human-readable outcome summary.', + }, + success: { + type: 'boolean', + description: 'Whether the operation succeeded.', }, - "success": { - "type": "boolean", - "description": "Whether the file operation succeeded." - } }, - "required": [ - "success", - "message" - ] + required: ['success', 'message'], + }, + }, + workflow: { + parameters: { + properties: { + request: { + description: + "A single sentence — the agent has full conversation context and VFS access. Do NOT look up IDs or pre-read data; the workflow agent does its own research. Example: 'move all the return letter workflows into a folder called Letters'.", + type: 'string', + }, + }, + required: ['request'], + type: 'object', + }, + resultSchema: undefined, + }, + workspace_file: { + parameters: { + type: 'object', + properties: { + operation: { + type: 'string', + description: 'The file operation to perform.', + enum: ['append', 'update', 'patch', 'rename', 'delete'], + }, + target: { + 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.', + }, + fileName: { + type: 'string', + description: + 'Plain workspace filename including extension, e.g. "main.py" or "report.docx". Required when target.kind=new_file.', + }, + }, + required: ['kind'], + }, + title: { + type: 'string', + description: + 'Optional short UI label for create/append chunks, e.g. "Chapter 1" or "Slide 3".', + }, + contentType: { + type: 'string', + description: + 'Optional MIME type override. Usually omit and let the system infer from the target file extension.', + enum: [ + 'text/markdown', + 'text/html', + 'text/plain', + 'application/json', + 'text/csv', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/pdf', + ], + }, + edit: { + type: 'object', + 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: + 'Boundary line kept after inserted replacement content. Required for mode=replace_between.', + }, + anchor: { + type: 'string', + description: + 'Anchor line after which new content is inserted. Required for mode=insert_after.', + }, + start_anchor: { + type: 'string', + description: 'First line to delete. Required for mode=delete_between.', + }, + end_anchor: { + type: 'string', + description: 'First line to keep after deletion. Required for mode=delete_between.', + }, + }, + }, + newName: { + type: 'string', + description: + 'New file name for rename. Must be a plain workspace filename like "main.py".', + }, + }, + required: ['operation', 'target'], + }, + 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 file operation succeeded.', + }, + }, + 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'], }, }, } diff --git a/apps/sim/lib/copilot/request/context/request-context.ts b/apps/sim/lib/copilot/request/context/request-context.ts index 003eef3c85..be1e82b130 100644 --- a/apps/sim/lib/copilot/request/context/request-context.ts +++ b/apps/sim/lib/copilot/request/context/request-context.ts @@ -25,6 +25,7 @@ export function createStreamingContext(overrides?: Partial): S streamComplete: false, wasAborted: false, errors: [], + activeFileIntent: null, trace: new TraceCollector(), ...overrides, } diff --git a/apps/sim/lib/copilot/request/go/stream.ts b/apps/sim/lib/copilot/request/go/stream.ts index 04f15e0055..63b63f3425 100644 --- a/apps/sim/lib/copilot/request/go/stream.ts +++ b/apps/sim/lib/copilot/request/go/stream.ts @@ -4,6 +4,7 @@ import { MothershipStreamV1EventType, MothershipStreamV1SpanLifecycleEvent, MothershipStreamV1SpanPayloadKind, + MothershipStreamV1ToolPhase, } from '@/lib/copilot/generated/mothership-stream-v1' import { processSSEStream } from '@/lib/copilot/request/go/parser' import { @@ -19,61 +20,28 @@ import type { StreamEvent, StreamingContext, } from '@/lib/copilot/request/types' +import { clearIntentsForWorkspace } from '@/lib/copilot/tools/server/files/file-intent-store' const logger = createLogger('CopilotGoStream') -type FilePreviewServerState = { - raw: string - started: boolean - operation?: string - targetKind?: string - fileId?: string - fileName?: string +type FileIntent = { + toolCallId: string + operation: string + target: { kind: string; fileId?: string; fileName?: string } title?: string - editMetaKey?: string - targetKey?: string + contentType?: string + edit?: Record +} + +type EditContentStreamState = { + raw: string lastContentSnapshot?: string } -function extractJsonString(raw: string, key: string): string | undefined { - const pattern = new RegExp(`"${key}"\\s*:\\s*"`) - const m = pattern.exec(raw) - if (!m) return undefined - const start = m.index + m[0].length - let end = -1 - for (let i = start; i < raw.length; i++) { - if (raw[i] === '\\') { - i++ - continue - } - if (raw[i] === '"') { - end = i - break - } - } - if (end === -1) return undefined - return raw - .slice(start, end) - .replace(/\\n/g, '\n') - .replace(/\\t/g, '\t') - .replace(/\\r/g, '\r') - .replace(/\\"/g, '"') - .replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16))) - .replace(/\\\\/g, '\\') -} - -function extractJsonBoolean(raw: string, key: string): boolean | undefined { - const match = raw.match(new RegExp(`"${key}"\\s*:\\s*(true|false)`)) - if (!match) return undefined - return match[1] === 'true' -} - -function extractJsonNumber(raw: string, key: string): number | undefined { - const match = raw.match(new RegExp(`"${key}"\\s*:\\s*(\\d+)`)) - if (!match) return undefined - return Number.parseInt(match[1], 10) -} - +/** + * Decode a prefix of a JSON-encoded string value, handling escape sequences + * that may be incomplete at the end of a streaming chunk. + */ function decodeJsonStringPrefix(input: string): string { let output = '' for (let i = 0; i < input.length; i++) { @@ -126,9 +94,7 @@ function decodeJsonStringPrefix(input: string): string { } if (next === 'u') { const hex = input.slice(i + 2, i + 6) - if (hex.length < 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) { - break - } + if (hex.length < 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) break output += String.fromCharCode(Number.parseInt(hex, 16)) i += 5 continue @@ -138,8 +104,13 @@ function decodeJsonStringPrefix(input: string): string { return output } -function extractStreamedContent(raw: string, preferredKey: 'content' | 'replace'): string { - const marker = `"${preferredKey}":` +/** + * Extract the streamed content string from edit_content's raw JSON args. + * Since edit_content has a single field `content`, the JSON is always + * `{"content":"..."}`. We find `"content":"` and decode everything after. + */ +function extractEditContent(raw: string): string { + const marker = '"content":' const idx = raw.indexOf(marker) if (idx === -1) return '' const rest = raw.slice(idx + marker.length).trimStart() @@ -159,13 +130,6 @@ function extractStreamedContent(raw: string, preferredKey: 'content' | 'replace' return decodeJsonStringPrefix(inner) } -function buildPreviewContent(raw: string, strategy?: string): string { - if (strategy === 'search_replace') { - return extractStreamedContent(raw, 'replace') - } - return extractStreamedContent(raw, 'content') -} - export class CopilotBackendError extends Error { status?: number body?: string @@ -202,9 +166,10 @@ export interface StreamLoopOptions extends OrchestratorOptions { * Handles: fetch -> parse -> normalize -> dedupe -> subagent routing -> handler dispatch. * Callers provide the fetch URL/options and can intercept events via onBeforeDispatch. * - * Optimised hot path: text events (the most frequent) bypass tool-call dedup - * checks and are dispatched synchronously without any await, eliminating ~4 - * microtask yields per text event vs the previous async-generator + await chain. + * File preview streaming uses an intent-based approach: + * 1. workspace_file phase:call → store intent (operation, target, edit metadata) + * 2. edit_content phase:args_delta → stream content using stored intent + * 3. edit_content phase:call → consume and clear intent */ export async function runStreamLoop( fetchUrl: string, @@ -214,7 +179,7 @@ export async function runStreamLoop( options: StreamLoopOptions ): Promise { const { timeout = ORCHESTRATION_TIMEOUT_MS, abortSignal } = options - const filePreviewState = new Map() + const editContentState = new Map() const fetchSpan = context.trace.startSpan( `HTTP Request → ${new URL(fetchUrl).pathname}`, @@ -277,144 +242,180 @@ export async function runStreamLoop( return } + // ── workspace_file phase:call → store intent and emit preview metadata ── if ( streamEvent.type === MothershipStreamV1EventType.tool && - streamEvent.payload.phase === 'args_delta' && - streamEvent.payload.toolName === 'workspace_file' && + streamEvent.payload.phase === MothershipStreamV1ToolPhase.call && + streamEvent.payload.toolName === 'workspace_file' + ) { + const toolCallId = streamEvent.payload.toolCallId as string | undefined + const args = (streamEvent.payload.arguments ?? streamEvent.payload.input) as + | Record + | undefined + if (toolCallId && args) { + const operation = args.operation as string | undefined + const target = args.target as Record | undefined + const title = args.title as string | undefined + const contentType = args.contentType as string | undefined + const edit = args.edit as Record | undefined + + if (operation && target) { + const targetKind = target.kind as string + const fileId = target.fileId as string | undefined + const fileName = target.fileName as string | undefined + + const isContentOp = + operation === 'append' || operation === 'update' || operation === 'patch' + if (context.activeFileIntent && isContentOp) { + logger.warn( + 'Orphaned workspace_file intent: content-op workspace_file arrived without edit_content for prior intent', + { + orphanedToolCallId: context.activeFileIntent.toolCallId, + orphanedOperation: context.activeFileIntent.operation, + newToolCallId: toolCallId, + newOperation: operation, + } + ) + const cleared = clearIntentsForWorkspace(execContext.workspaceId) + if (cleared > 0) { + logger.warn('Cleared orphaned execution intents from store', { + cleared, + workspaceId: execContext.workspaceId, + }) + } + } + + context.activeFileIntent = { + toolCallId, + operation, + target: { + kind: targetKind, + ...(fileId ? { fileId } : {}), + ...(fileName ? { fileName } : {}), + }, + ...(title ? { title } : {}), + ...(contentType ? { contentType } : {}), + ...(edit ? { edit } : {}), + } + + const isDocFormat = /\.(pptx|docx|pdf)$/i.test(fileName ?? '') + if (!isDocFormat && isContentOp) { + const scope = streamEvent.scope ? { scope: streamEvent.scope } : {} + await options.onEvent?.({ + type: MothershipStreamV1EventType.tool, + payload: { + toolCallId, + toolName: 'workspace_file', + previewPhase: 'file_preview_start', + }, + ...scope, + }) + await options.onEvent?.({ + type: MothershipStreamV1EventType.tool, + payload: { + toolCallId, + toolName: 'workspace_file', + previewPhase: 'file_preview_target', + operation, + target: { + kind: targetKind, + ...(fileId ? { fileId } : {}), + ...(fileName ? { fileName } : {}), + }, + ...(title ? { title } : {}), + }, + ...scope, + }) + if (edit) { + await options.onEvent?.({ + type: MothershipStreamV1EventType.tool, + payload: { + toolCallId, + toolName: 'workspace_file', + previewPhase: 'file_preview_edit_meta', + edit, + }, + ...scope, + }) + } + } + } + } + } + + // ── edit_content phase:args_delta → stream content using stored intent ── + if ( + streamEvent.type === MothershipStreamV1EventType.tool && + streamEvent.payload.phase === MothershipStreamV1ToolPhase.args_delta && + streamEvent.payload.toolName === 'edit_content' && typeof streamEvent.payload.toolCallId === 'string' && typeof streamEvent.payload.argumentsDelta === 'string' ) { const toolCallId = streamEvent.payload.toolCallId as string const delta = streamEvent.payload.argumentsDelta as string - const state = filePreviewState.get(toolCallId) ?? { - raw: '', - started: false, - } + const state = editContentState.get(toolCallId) ?? { raw: '' } state.raw += delta - const operation = extractJsonString(state.raw, 'operation') - const targetKind = extractJsonString(state.raw, 'kind') - const fileId = extractJsonString(state.raw, 'fileId') - const fileName = extractJsonString(state.raw, 'fileName') - const title = extractJsonString(state.raw, 'title') - if (operation) state.operation = operation - if (targetKind) state.targetKind = targetKind - if (fileId) state.fileId = fileId - if (fileName) state.fileName = fileName - if (title) state.title = title - - const isDocFormat = /\.(pptx|docx|pdf)$/i.test(state.fileName ?? '') - if (!isDocFormat) { - if (!state.started) { - state.started = true - await options.onEvent?.({ - type: MothershipStreamV1EventType.tool, - payload: { - toolCallId, - toolName: 'workspace_file', - previewPhase: 'file_preview_start', - }, - ...(streamEvent.scope ? { scope: streamEvent.scope } : {}), - }) - } - - const targetKey = JSON.stringify({ - operation: state.operation, - targetKind: state.targetKind, - fileId: state.fileId, - fileName: state.fileName, - title: state.title, - }) - if ( - state.targetKind && - (state.targetKind === 'new_file' ? !!state.fileName : !!state.fileId) && - state.targetKey !== targetKey - ) { - state.targetKey = targetKey - await options.onEvent?.({ - type: MothershipStreamV1EventType.tool, - payload: { - toolCallId, - toolName: 'workspace_file', - previewPhase: 'file_preview_target', - operation: state.operation, - target: { - kind: state.targetKind, - ...(state.fileId ? { fileId: state.fileId } : {}), - ...(state.fileName ? { fileName: state.fileName } : {}), + if (context.activeFileIntent) { + const isDocFormat = /\.(pptx|docx|pdf)$/i.test( + context.activeFileIntent.target.fileName ?? '' + ) + if (!isDocFormat) { + const streamedContent = extractEditContent(state.raw) + if (streamedContent !== (state.lastContentSnapshot ?? '')) { + state.lastContentSnapshot = streamedContent + await options.onEvent?.({ + type: MothershipStreamV1EventType.tool, + payload: { + toolCallId: context.activeFileIntent.toolCallId, + toolName: 'workspace_file', + previewPhase: 'file_preview_content', + content: streamedContent, + contentMode: 'snapshot', }, - ...(state.title ? { title: state.title } : {}), - }, - ...(streamEvent.scope ? { scope: streamEvent.scope } : {}), - }) + ...(streamEvent.scope ? { scope: streamEvent.scope } : {}), + }) + } } + } - const strategy = extractJsonString(state.raw, 'strategy') - const editMetaPayload = strategy - ? { - strategy, - ...(extractJsonString(state.raw, 'mode') - ? { mode: extractJsonString(state.raw, 'mode') } - : {}), - ...(extractJsonNumber(state.raw, 'occurrence') !== undefined - ? { occurrence: extractJsonNumber(state.raw, 'occurrence') } - : {}), - ...(extractJsonString(state.raw, 'search') - ? { search: extractJsonString(state.raw, 'search') } - : {}), - ...(extractJsonBoolean(state.raw, 'replaceAll') !== undefined - ? { replaceAll: extractJsonBoolean(state.raw, 'replaceAll') } - : {}), - ...(extractJsonString(state.raw, 'before_anchor') - ? { before_anchor: extractJsonString(state.raw, 'before_anchor') } - : {}), - ...(extractJsonString(state.raw, 'after_anchor') - ? { after_anchor: extractJsonString(state.raw, 'after_anchor') } - : {}), - ...(extractJsonString(state.raw, 'anchor') - ? { anchor: extractJsonString(state.raw, 'anchor') } - : {}), - ...(extractJsonString(state.raw, 'start_anchor') - ? { start_anchor: extractJsonString(state.raw, 'start_anchor') } - : {}), - ...(extractJsonString(state.raw, 'end_anchor') - ? { end_anchor: extractJsonString(state.raw, 'end_anchor') } - : {}), - } - : undefined - const editMetaKey = editMetaPayload ? JSON.stringify(editMetaPayload) : undefined - if (editMetaPayload && state.editMetaKey !== editMetaKey) { - state.editMetaKey = editMetaKey - await options.onEvent?.({ - type: MothershipStreamV1EventType.tool, - payload: { - toolCallId, - toolName: 'workspace_file', - previewPhase: 'file_preview_edit_meta', - edit: editMetaPayload, - }, - ...(streamEvent.scope ? { scope: streamEvent.scope } : {}), - }) - } + editContentState.set(toolCallId, state) + } - const streamedContent = buildPreviewContent(state.raw, strategy) - if (streamedContent !== (state.lastContentSnapshot ?? '')) { - state.lastContentSnapshot = streamedContent - await options.onEvent?.({ - type: MothershipStreamV1EventType.tool, - payload: { - toolCallId, - toolName: 'workspace_file', - previewPhase: 'file_preview_content', - content: streamedContent, - contentMode: 'snapshot', - }, - ...(streamEvent.scope ? { scope: streamEvent.scope } : {}), - }) - } - } // end if (!isDocFormat) + // ── edit_content phase:call → keep intent until result for preview completion ── + if ( + streamEvent.type === MothershipStreamV1EventType.tool && + streamEvent.payload.phase === MothershipStreamV1ToolPhase.call && + streamEvent.payload.toolName === 'edit_content' + ) { + const toolCallId = streamEvent.payload.toolCallId as string | undefined + if (toolCallId) { + editContentState.delete(toolCallId) + } + } - filePreviewState.set(toolCallId, state) + // ── edit_content phase:result → complete preview and clear intent ── + if ( + streamEvent.type === MothershipStreamV1EventType.tool && + streamEvent.payload.phase === MothershipStreamV1ToolPhase.result && + streamEvent.payload.toolName === 'edit_content' && + context.activeFileIntent + ) { + await options.onEvent?.({ + type: MothershipStreamV1EventType.tool, + payload: { + toolCallId: context.activeFileIntent.toolCallId, + toolName: 'workspace_file', + previewPhase: 'file_preview_complete', + fileId: context.activeFileIntent.target.fileId, + data: + streamEvent.payload.result !== undefined + ? streamEvent.payload.result + : streamEvent.payload.data, + }, + ...(streamEvent.scope ? { scope: streamEvent.scope } : {}), + }) + context.activeFileIntent = null } try { diff --git a/apps/sim/lib/copilot/request/lifecycle/run.ts b/apps/sim/lib/copilot/request/lifecycle/run.ts index f5d15cf767..0da134c1d5 100644 --- a/apps/sim/lib/copilot/request/lifecycle/run.ts +++ b/apps/sim/lib/copilot/request/lifecycle/run.ts @@ -193,10 +193,17 @@ async function runCheckpointLoop( execContext, loopOptions ) - context.trace.endSpan(streamSpan) + const streamStatus = isAborted(options, context) + ? RequestTraceV1SpanStatus.cancelled + : context.errors.length > 0 + ? RequestTraceV1SpanStatus.error + : RequestTraceV1SpanStatus.ok + context.trace.endSpan(streamSpan, streamStatus) + context.trace.setActiveSpan(undefined) resumeAttempt = 0 } catch (streamError) { context.trace.endSpan(streamSpan, RequestTraceV1SpanStatus.error) + context.trace.setActiveSpan(undefined) if (streamError instanceof BillingLimitError) { await handleBillingLimitResponse(streamError.userId, context, execContext, options) break @@ -282,6 +289,12 @@ async function runCheckpointLoop( ) } + if (isAborted(options, context)) { + cancelPendingTools(context) + context.awaitingAsyncContinuation = undefined + break + } + const results: Array<{ callId: string name: string @@ -289,6 +302,11 @@ async function runCheckpointLoop( success: boolean }> = [] for (const toolCallId of continuation.pendingToolCallIds) { + if (isAborted(options, context)) { + cancelPendingTools(context) + context.awaitingAsyncContinuation = undefined + break + } const tool = context.toolCalls.get(toolCallId) if (!tool || (!tool.result && !tool.error)) { logger.error('Missing tool result for pending tool call', { @@ -309,6 +327,12 @@ async function runCheckpointLoop( }) } + if (isAborted(options, context)) { + cancelPendingTools(context) + context.awaitingAsyncContinuation = undefined + break + } + logger.info('Resuming with tool results', { checkpointId: continuation.checkpointId, runId: continuation.runId, @@ -324,6 +348,13 @@ async function runCheckpointLoop( checkpointId: continuation.checkpointId, results, } + + if (isAborted(options, context)) { + cancelPendingTools(context) + context.awaitingAsyncContinuation = undefined + break + } + logger.info('Prepared resume request payload', { route, streamId: context.messageId, diff --git a/apps/sim/lib/copilot/request/tools/executor.ts b/apps/sim/lib/copilot/request/tools/executor.ts index dfe52ae897..4c3c90d0ae 100644 --- a/apps/sim/lib/copilot/request/tools/executor.ts +++ b/apps/sim/lib/copilot/request/tools/executor.ts @@ -211,6 +211,19 @@ export async function executeToolAndReport( toolSpan.attributes = { ...toolSpan.attributes, ...abortDetail, ...detail } context.trace.endSpan(toolSpan, status) } + const endToolSpanFromTerminalState = () => { + const terminalStatus = + toolCall.status === MothershipStreamV1ToolOutcome.cancelled + ? 'cancelled' + : toolCall.status === MothershipStreamV1ToolOutcome.success || + toolCall.status === MothershipStreamV1ToolOutcome.skipped + ? 'ok' + : 'error' + endToolSpan(terminalStatus, { + resultSuccess: toolCall.status === MothershipStreamV1ToolOutcome.success, + ...(toolCall.error ? { error: toolCall.error } : {}), + }) + } logger.info('Tool execution started', { toolCallId: toolCall.id, @@ -221,6 +234,7 @@ export async function executeToolAndReport( ensureHandlersRegistered() let result = await executeTool(toolCall.name, toolCall.params || {}, execContext) if (toolCall.endTime || isTerminalToolCallStatus(toolCall.status)) { + endToolSpanFromTerminalState() return terminalCompletionFromToolCall(toolCall) } if (abortRequested(context, execContext, options)) { @@ -394,6 +408,7 @@ export async function executeToolAndReport( if (abortRequested(context, execContext, options)) { toolCall.status = MothershipStreamV1ToolOutcome.cancelled + endToolSpan('cancelled', { cancelReason: 'abort_before_tool_result_delivery' }) return cancelledCompletion('Request aborted before tool result delivery') } @@ -418,6 +433,7 @@ export async function executeToolAndReport( if (abortRequested(context, execContext, options)) { toolCall.status = MothershipStreamV1ToolOutcome.cancelled + endToolSpan('cancelled', { cancelReason: 'abort_before_resource_persistence' }) return cancelledCompletion('Request aborted before resource persistence') } diff --git a/apps/sim/lib/copilot/request/types.ts b/apps/sim/lib/copilot/request/types.ts index e4aa1cb3ff..96a8454f36 100644 --- a/apps/sim/lib/copilot/request/types.ts +++ b/apps/sim/lib/copilot/request/types.ts @@ -86,6 +86,14 @@ export interface StreamingContext { errors: string[] usage?: { prompt: number; completion: number } cost?: { input: number; output: number; total: number } + activeFileIntent?: { + toolCallId: string + operation: string + target: { kind: string; fileId?: string; fileName?: string } + title?: string + contentType?: string + edit?: Record + } | null trace: TraceCollector } diff --git a/apps/sim/lib/copilot/tools/server/files/edit-content.ts b/apps/sim/lib/copilot/tools/server/files/edit-content.ts new file mode 100644 index 0000000000..de32aff1a4 --- /dev/null +++ b/apps/sim/lib/copilot/tools/server/files/edit-content.ts @@ -0,0 +1,287 @@ +import { createLogger } from '@sim/logger' +import { + assertServerToolNotAborted, + type BaseServerTool, + type ServerToolContext, +} from '@/lib/copilot/tools/server/base-tool' +import { + generateDocxFromCode, + generatePdfFromCode, + generatePptxFromCode, +} from '@/lib/execution/doc-vm' +import { updateWorkspaceFileContent } from '@/lib/uploads/contexts/workspace/workspace-file-manager' +import { consumeLatestFileIntent } from './file-intent-store' +import { inferContentType } from './workspace-file' + +const logger = createLogger('EditContentServerTool') + +type EditContentArgs = { + content: string +} + +type EditContentResult = { + success: boolean + message: string + data?: Record +} + +function getDocumentFormatInfo(fileName: string): { + isDoc: boolean + formatName?: string + sourceMime?: string + generator?: (code: string, workspaceId: string, signal?: AbortSignal) => Promise +} { + const lowerName = fileName.toLowerCase() + if (lowerName.endsWith('.pptx')) { + return { + isDoc: true, + formatName: 'PPTX', + sourceMime: 'text/x-pptxgenjs', + generator: generatePptxFromCode, + } + } + if (lowerName.endsWith('.docx')) { + return { + isDoc: true, + formatName: 'DOCX', + sourceMime: 'text/x-docxjs', + generator: generateDocxFromCode, + } + } + if (lowerName.endsWith('.pdf')) { + return { + isDoc: true, + formatName: 'PDF', + sourceMime: 'text/x-pdflibjs', + generator: generatePdfFromCode, + } + } + return { isDoc: false } +} + +export const editContentServerTool: BaseServerTool = { + name: 'edit_content', + async execute(params: EditContentArgs, context?: ServerToolContext): Promise { + if (!context?.userId) { + logger.error('Unauthorized attempt to use edit_content') + throw new Error('Authentication required') + } + + const workspaceId = context.workspaceId + if (!workspaceId) { + return { success: false, message: 'Workspace ID is required' } + } + + const raw = params as Record + const nested = raw.args as Record | undefined + const content = + typeof params.content === 'string' + ? params.content + : typeof nested?.content === 'string' + ? (nested.content as string) + : undefined + + if (content === undefined) { + return { success: false, message: 'content is required for edit_content' } + } + + const intent = consumeLatestFileIntent(workspaceId) + if (!intent) { + return { + success: false, + message: + 'No workspace_file context found. Call workspace_file first, wait for it to succeed, then call edit_content in the next step. Do not emit edit_content in parallel or in the same batch as workspace_file.', + } + } + + try { + const { operation, fileRecord } = intent + const docInfo = getDocumentFormatInfo(fileRecord.name) + + let finalContent: string + switch (operation) { + case 'append': { + const existing = intent.existingContent ?? '' + finalContent = docInfo.isDoc + ? `${existing}\n{\n${content}\n}` + : existing + ? `${existing}\n${content}` + : content + break + } + case 'update': { + finalContent = content + break + } + case 'patch': { + const existing = intent.existingContent ?? '' + if (!intent.edit) { + return { success: false, message: 'Patch intent missing edit metadata' } + } + + if (intent.edit.strategy === 'search_replace') { + const search = intent.edit.search! + const firstIdx = existing.indexOf(search) + if (firstIdx === -1) { + return { + success: false, + message: `Patch failed: search string not found in file "${fileRecord.name}"`, + } + } + finalContent = intent.edit.replaceAll + ? existing.split(search).join(content) + : existing.slice(0, firstIdx) + content + existing.slice(firstIdx + search.length) + } else if (intent.edit.strategy === 'anchored') { + const lines = existing.split('\n') + const defaultOccurrence = intent.edit.occurrence ?? 1 + + const findAnchorLine = ( + anchor: string, + occurrence = defaultOccurrence, + afterIndex = -1 + ): { index: number; error?: string } => { + const trimmed = anchor.trim() + let count = 0 + for (let i = afterIndex + 1; i < lines.length; i++) { + if (lines[i].trim() === trimmed) { + count++ + if (count === occurrence) return { index: i } + } + } + if (count === 0) { + return { + index: -1, + error: `Anchor line not found in "${fileRecord.name}": "${anchor.slice(0, 100)}"`, + } + } + return { + index: -1, + error: `Anchor line occurrence ${occurrence} not found (only ${count} match${count > 1 ? 'es' : ''}) in "${fileRecord.name}": "${anchor.slice(0, 100)}"`, + } + } + + if (intent.edit.mode === 'replace_between') { + if (!intent.edit.before_anchor || !intent.edit.after_anchor) { + return { + success: false, + message: 'replace_between requires before_anchor and after_anchor', + } + } + const before = findAnchorLine(intent.edit.before_anchor) + if (before.error) return { success: false, message: `Patch failed: ${before.error}` } + const after = findAnchorLine( + intent.edit.after_anchor, + defaultOccurrence, + before.index + ) + if (after.error) return { success: false, message: `Patch failed: ${after.error}` } + if (after.index <= before.index) { + return { + success: false, + message: 'Patch failed: after_anchor must appear after before_anchor in the file', + } + } + const newLines = [ + ...lines.slice(0, before.index + 1), + ...content.split('\n'), + ...lines.slice(after.index), + ] + finalContent = newLines.join('\n') + } else if (intent.edit.mode === 'insert_after') { + if (!intent.edit.anchor) { + return { success: false, message: 'insert_after requires anchor' } + } + const found = findAnchorLine(intent.edit.anchor) + if (found.error) return { success: false, message: `Patch failed: ${found.error}` } + const newLines = [ + ...lines.slice(0, found.index + 1), + ...content.split('\n'), + ...lines.slice(found.index + 1), + ] + finalContent = newLines.join('\n') + } else if (intent.edit.mode === 'delete_between') { + if (!intent.edit.start_anchor || !intent.edit.end_anchor) { + return { + success: false, + message: 'delete_between requires start_anchor and end_anchor', + } + } + const start = findAnchorLine(intent.edit.start_anchor) + if (start.error) return { success: false, message: `Patch failed: ${start.error}` } + const end = findAnchorLine(intent.edit.end_anchor, defaultOccurrence, start.index) + if (end.error) return { success: false, message: `Patch failed: ${end.error}` } + if (end.index <= start.index) { + return { + success: false, + message: 'Patch failed: end_anchor must appear after start_anchor in the file', + } + } + const newLines = [...lines.slice(0, start.index), ...lines.slice(end.index)] + finalContent = newLines.join('\n') + } else { + return { + success: false, + message: `Unknown anchored patch mode: "${intent.edit.mode}"`, + } + } + } else { + return { success: false, message: `Unknown patch strategy: "${intent.edit.strategy}"` } + } + break + } + default: + return { success: false, message: `Unsupported operation in intent: ${operation}` } + } + + if (docInfo.isDoc) { + try { + await docInfo.generator!(finalContent, workspaceId) + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + return { + success: false, + message: `${docInfo.formatName} generation failed: ${msg}. Fix the content and retry.`, + } + } + } + + const fileBuffer = Buffer.from(finalContent, 'utf-8') + assertServerToolNotAborted(context) + const mime = docInfo.sourceMime || inferContentType(fileRecord.name, intent.contentType) + await updateWorkspaceFileContent(workspaceId, intent.fileId, context.userId, fileBuffer, mime) + + const verb = + operation === 'append' ? 'appended to' : operation === 'update' ? 'updated' : 'patched' + logger.info(`Workspace file ${verb} via copilot (edit_content)`, { + fileId: intent.fileId, + name: fileRecord.name, + operation, + size: fileBuffer.length, + userId: context.userId, + }) + + return { + success: true, + message: `File "${fileRecord.name}" ${verb} successfully (${fileBuffer.length} bytes)`, + data: { + id: intent.fileId, + name: fileRecord.name, + size: fileBuffer.length, + contentType: mime, + }, + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' + logger.error('Error in edit_content tool', { + operation: intent.operation, + fileId: intent.fileId, + error: errorMessage, + userId: context.userId, + }) + return { + success: false, + message: `Failed to apply content: ${errorMessage}`, + } + } + }, +} diff --git a/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts b/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts new file mode 100644 index 0000000000..5127666390 --- /dev/null +++ b/apps/sim/lib/copilot/tools/server/files/file-intent-store.ts @@ -0,0 +1,90 @@ +import type { UserFile } from '@/lib/uploads/contexts/workspace/workspace-file-manager' + +export type PendingFileIntent = { + operation: 'append' | 'update' | 'patch' + fileId: string + workspaceId: string + userId: string + fileRecord: UserFile + existingContent?: string + edit?: { + strategy: string + search?: string + replaceAll?: boolean + mode?: string + occurrence?: number + before_anchor?: string + after_anchor?: string + anchor?: string + start_anchor?: string + end_anchor?: string + } + contentType?: string + title?: string + createdAt: number +} + +const INTENT_TTL_MS = 60_000 +const store = new Map() + +function buildKey(workspaceId: string, fileId: string): string { + return `${workspaceId}:${fileId}` +} + +function cleanupStale(): void { + const now = Date.now() + for (const [key, intent] of store) { + if (now - intent.createdAt > INTENT_TTL_MS) { + store.delete(key) + } + } +} + +export function storeFileIntent( + workspaceId: string, + fileId: string, + intent: PendingFileIntent +): void { + cleanupStale() + store.set(buildKey(workspaceId, fileId), intent) +} + +export function consumeFileIntent( + workspaceId: string, + fileId: string +): PendingFileIntent | undefined { + const key = buildKey(workspaceId, fileId) + const intent = store.get(key) + if (intent) { + store.delete(key) + } + return intent +} + +export function consumeLatestFileIntent(workspaceId: string): PendingFileIntent | undefined { + let latest: PendingFileIntent | undefined + let latestKey: string | undefined + for (const [key, intent] of store) { + if (intent.workspaceId === workspaceId) { + if (!latest || intent.createdAt > latest.createdAt) { + latest = intent + latestKey = key + } + } + } + if (latestKey) { + store.delete(latestKey) + } + return latest +} + +export function clearIntentsForWorkspace(workspaceId: string): number { + let cleared = 0 + for (const [key, intent] of store) { + if (intent.workspaceId === workspaceId) { + store.delete(key) + cleared++ + } + } + return cleared +} diff --git a/apps/sim/lib/copilot/tools/server/files/workspace-file.ts b/apps/sim/lib/copilot/tools/server/files/workspace-file.ts index a161d3b0b7..dd23523083 100644 --- a/apps/sim/lib/copilot/tools/server/files/workspace-file.ts +++ b/apps/sim/lib/copilot/tools/server/files/workspace-file.ts @@ -16,9 +16,9 @@ import { getWorkspaceFile, getWorkspaceFileByName, renameWorkspaceFile, - updateWorkspaceFileContent, uploadWorkspaceFile, } from '@/lib/uploads/contexts/workspace/workspace-file-manager' +import { storeFileIntent } from './file-intent-store' const logger = createLogger('WorkspaceFileServerTool') @@ -251,9 +251,6 @@ export const workspaceFileServerTool: BaseServerTool { - const trimmed = anchor.trim() - let count = 0 - for (let i = afterIndex + 1; i < lines.length; i++) { - if (lines[i].trim() === trimmed) { - count++ - if (count === occurrence) return { index: i } - } - } - if (count === 0) { - return { - index: -1, - error: `Anchor line not found in "${fileRecord.name}": "${anchor.slice(0, 100)}"`, - } - } - return { - index: -1, - error: `Anchor line occurrence ${occurrence} not found (only ${count} match${count > 1 ? 'es' : ''}) in "${fileRecord.name}": "${anchor.slice(0, 100)}"`, - } - } - - if (normalized.edit.mode === 'replace_between') { - if (!normalized.edit.before_anchor || !normalized.edit.after_anchor) { - return { - success: false, - message: 'replace_between requires before_anchor and after_anchor', - } - } - const before = findAnchorLine(normalized.edit.before_anchor) - if (before.error) return { success: false, message: `Patch failed: ${before.error}` } - const after = findAnchorLine( - normalized.edit.after_anchor, - defaultOccurrence, - before.index - ) - if (after.error) return { success: false, message: `Patch failed: ${after.error}` } - if (after.index <= before.index) { - return { - success: false, - message: 'Patch failed: after_anchor must appear after before_anchor in the file', - } - } - const newLines = [ - ...lines.slice(0, before.index + 1), - ...(normalized.edit.content ?? '').split('\n'), - ...lines.slice(after.index), - ] - content = newLines.join('\n') - } else if (normalized.edit.mode === 'insert_after') { - if (!normalized.edit.anchor) { - return { success: false, message: 'insert_after requires anchor' } - } - const found = findAnchorLine(normalized.edit.anchor) - if (found.error) return { success: false, message: `Patch failed: ${found.error}` } - const newLines = [ - ...lines.slice(0, found.index + 1), - ...(normalized.edit.content ?? '').split('\n'), - ...lines.slice(found.index + 1), - ] - content = newLines.join('\n') - } else if (normalized.edit.mode === 'delete_between') { - if (!normalized.edit.start_anchor || !normalized.edit.end_anchor) { - return { - success: false, - message: 'delete_between requires start_anchor and end_anchor', - } - } - const start = findAnchorLine(normalized.edit.start_anchor) - if (start.error) return { success: false, message: `Patch failed: ${start.error}` } - const end = findAnchorLine(normalized.edit.end_anchor, defaultOccurrence, start.index) - if (end.error) return { success: false, message: `Patch failed: ${end.error}` } - if (end.index <= start.index) { - return { - success: false, - message: 'Patch failed: end_anchor must appear after start_anchor in the file', - } - } - const newLines = [...lines.slice(0, start.index), ...lines.slice(end.index)] - content = newLines.join('\n') - } else { - return { - success: false, - message: `Unknown anchored patch mode: "${normalized.edit.mode}"`, - } - } - } else if (normalized.edit.strategy === 'search_replace') { + if (normalized.edit.strategy === 'search_replace') { const search = normalized.edit.search - const replace = normalized.edit.replace - const firstIdx = content.indexOf(search) + const firstIdx = existingContent.indexOf(search) if (firstIdx === -1) { return { success: false, message: `Patch failed: search string not found in file "${fileRecord.name}". Search: "${search.slice(0, 100)}${search.length > 100 ? '...' : ''}"`, } } - if (!normalized.edit.replaceAll && content.indexOf(search, firstIdx + 1) !== -1) { + if ( + !normalized.edit.replaceAll && + existingContent.indexOf(search, firstIdx + 1) !== -1 + ) { return { success: false, message: `Patch failed: search string is ambiguous — found at multiple locations in "${fileRecord.name}". Use a longer unique search string or replaceAll.`, } } - content = normalized.edit.replaceAll - ? content.split(search).join(replace) - : content.slice(0, firstIdx) + replace + content.slice(firstIdx + search.length) + } else if (normalized.edit.strategy === 'anchored') { + if (!normalized.edit.mode) { + return { success: false, message: 'anchored strategy requires mode' } + } } else { return { success: false, @@ -589,46 +441,41 @@ export const workspaceFileServerTool: BaseServerTool = { [ManageSkill.id]: ['add', 'edit', 'delete'], [ManageCredential.id]: ['rename', 'delete'], [WorkspaceFile.id]: ['create', 'append', 'update', 'delete', 'rename', 'patch'], + [editContentServerTool.name]: ['*'], [CreateFile.id]: ['*'], [RenameFile.id]: ['*'], [DeleteFile.id]: ['*'], @@ -128,6 +130,7 @@ const serverToolRegistry: Record = { [knowledgeBaseServerTool.name]: knowledgeBaseServerTool, [userTableServerTool.name]: userTableServerTool, [workspaceFileServerTool.name]: workspaceFileServerTool, + [editContentServerTool.name]: editContentServerTool, [createFileServerTool.name]: createFileServerTool, [renameFileServerTool.name]: renameFileServerTool, [deleteFileServerTool.name]: deleteFileServerTool,