Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add copilot validation
  • Loading branch information
Sg312 committed Dec 5, 2025
commit 68d9f67bdd5088e0e2dbac80d73385f8d84d56f0
60 changes: 48 additions & 12 deletions apps/sim/lib/copilot/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const ToolIds = z.enum([
'reason',
'list_user_workflows',
'get_workflow_from_name',
'get_global_workflow_variables',
'get_workflow_data',
'set_global_workflow_variables',
'oauth_request_access',
'get_trigger_blocks',
Expand Down Expand Up @@ -57,8 +57,10 @@ export const ToolArgSchemas = {
// New tools
list_user_workflows: z.object({}),
get_workflow_from_name: z.object({ workflow_name: z.string() }),
// New variable tools
get_global_workflow_variables: z.object({}),
// Workflow data tool (variables, custom tools, MCP tools, files)
get_workflow_data: z.object({
data_type: z.enum(['global_variables', 'custom_tools', 'mcp_tools', 'files']),
}),
set_global_workflow_variables: z.object({
operations: z.array(
z.object({
Expand Down Expand Up @@ -209,11 +211,8 @@ export const ToolSSESchemas = {
'get_workflow_from_name',
ToolArgSchemas.get_workflow_from_name
),
// New variable tools
get_global_workflow_variables: toolCallSSEFor(
'get_global_workflow_variables',
ToolArgSchemas.get_global_workflow_variables
),
// Workflow data tool (variables, custom tools, MCP tools, files)
get_workflow_data: toolCallSSEFor('get_workflow_data', ToolArgSchemas.get_workflow_data),
set_global_workflow_variables: toolCallSSEFor(
'set_global_workflow_variables',
ToolArgSchemas.set_global_workflow_variables
Expand Down Expand Up @@ -299,10 +298,47 @@ export const ToolResultSchemas = {
.object({ yamlContent: z.string() })
.or(z.object({ userWorkflow: z.string() }))
.or(z.string()),
// New variable tools
get_global_workflow_variables: z
.object({ variables: z.record(z.any()) })
.or(z.array(z.object({ name: z.string(), value: z.any() }))),
// Workflow data tool results (variables, custom tools, MCP tools, files)
get_workflow_data: z.union([
z.object({
variables: z.array(z.object({ id: z.string(), name: z.string(), value: z.any() })),
}),
z.object({
customTools: z.array(
z.object({
id: z.string(),
title: z.string(),
functionName: z.string(),
description: z.string(),
parameters: z.any().optional(),
})
),
}),
z.object({
mcpTools: z.array(
z.object({
name: z.string(),
serverId: z.string(),
serverName: z.string(),
description: z.string(),
inputSchema: z.any().optional(),
})
),
}),
z.object({
files: z.array(
z.object({
id: z.string(),
name: z.string(),
key: z.string(),
path: z.string(),
size: z.number(),
type: z.string(),
uploadedAt: z.string(),
})
),
}),
]),
set_global_workflow_variables: z
.object({ variables: z.record(z.any()) })
.or(z.object({ message: z.any().optional(), data: z.any().optional() })),
Expand Down
91 changes: 87 additions & 4 deletions apps/sim/lib/copilot/tools/client/workflow/edit-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,44 @@ export class EditWorkflowClientTool extends BaseClientTool {
// Get the workflow state that was applied, merge subblocks, and sanitize
// This matches what get_user_workflow would return
const workflowJson = this.getSanitizedWorkflowJson(currentState)
const sanitizedData = workflowJson ? { userWorkflow: workflowJson } : undefined

await this.markToolComplete(200, 'Workflow edits accepted', sanitizedData)
// Build sanitized data including workflow JSON and any skipped/validation info from the result
const sanitizedData: Record<string, any> = {}
if (workflowJson) {
sanitizedData.userWorkflow = workflowJson
}

// Include skipped items and validation errors in the accept response for LLM feedback
if (this.lastResult?.skippedItems?.length > 0) {
sanitizedData.skippedItems = this.lastResult.skippedItems
sanitizedData.skippedItemsMessage = this.lastResult.skippedItemsMessage
}
if (this.lastResult?.inputValidationErrors?.length > 0) {
sanitizedData.inputValidationErrors = this.lastResult.inputValidationErrors
sanitizedData.inputValidationMessage = this.lastResult.inputValidationMessage
}

// Build a message that includes info about skipped items
let acceptMessage = 'Workflow edits accepted'
if (
this.lastResult?.skippedItems?.length > 0 ||
this.lastResult?.inputValidationErrors?.length > 0
) {
const parts: string[] = []
if (this.lastResult?.skippedItems?.length > 0) {
parts.push(`${this.lastResult.skippedItems.length} operation(s) were skipped`)
}
if (this.lastResult?.inputValidationErrors?.length > 0) {
parts.push(`${this.lastResult.inputValidationErrors.length} input(s) were rejected`)
}
acceptMessage = `Workflow edits accepted. Note: ${parts.join(', ')}.`
}

await this.markToolComplete(
200,
acceptMessage,
Object.keys(sanitizedData).length > 0 ? sanitizedData : undefined
)
this.setState(ClientToolCallState.success)
}

Expand Down Expand Up @@ -248,8 +283,24 @@ export class EditWorkflowClientTool extends BaseClientTool {
blocksCount: result?.workflowState
? Object.keys(result.workflowState.blocks || {}).length
: 0,
hasSkippedItems: !!result?.skippedItems,
skippedItemsCount: result?.skippedItems?.length || 0,
hasInputValidationErrors: !!result?.inputValidationErrors,
inputValidationErrorsCount: result?.inputValidationErrors?.length || 0,
})

// Log skipped items and validation errors for visibility
if (result?.skippedItems?.length > 0) {
logger.warn('Some operations were skipped during edit_workflow', {
skippedItems: result.skippedItems,
})
}
if (result?.inputValidationErrors?.length > 0) {
logger.warn('Some inputs were rejected during edit_workflow', {
inputValidationErrors: result.inputValidationErrors,
})
}

// Update diff directly with workflow state - no YAML conversion needed!
// The diff engine may transform the workflow state (e.g., assign new IDs), so we must use
// the returned proposedState rather than the original result.workflowState
Expand Down Expand Up @@ -288,10 +339,42 @@ export class EditWorkflowClientTool extends BaseClientTool {
// Get the workflow state that was just applied, merge subblocks, and sanitize
// This matches what get_user_workflow would return (the true state after edits were applied)
const workflowJson = this.getSanitizedWorkflowJson(actualDiffWorkflow)
const sanitizedData = workflowJson ? { userWorkflow: workflowJson } : undefined

// Build sanitized data including workflow JSON and any skipped/validation info
const sanitizedData: Record<string, any> = {}
if (workflowJson) {
sanitizedData.userWorkflow = workflowJson
}

// Include skipped items and validation errors in the response for LLM feedback
if (result?.skippedItems?.length > 0) {
sanitizedData.skippedItems = result.skippedItems
sanitizedData.skippedItemsMessage = result.skippedItemsMessage
}
if (result?.inputValidationErrors?.length > 0) {
sanitizedData.inputValidationErrors = result.inputValidationErrors
sanitizedData.inputValidationMessage = result.inputValidationMessage
}

// Build a message that includes info about skipped items
let completeMessage = 'Workflow diff ready for review'
if (result?.skippedItems?.length > 0 || result?.inputValidationErrors?.length > 0) {
const parts: string[] = []
if (result?.skippedItems?.length > 0) {
parts.push(`${result.skippedItems.length} operation(s) skipped`)
}
if (result?.inputValidationErrors?.length > 0) {
parts.push(`${result.inputValidationErrors.length} input(s) rejected`)
}
completeMessage = `Workflow diff ready for review. Note: ${parts.join(', ')}.`
}

// Mark complete early to unblock LLM stream
await this.markToolComplete(200, 'Workflow diff ready for review', sanitizedData)
await this.markToolComplete(
200,
completeMessage,
Object.keys(sanitizedData).length > 0 ? sanitizedData : undefined
)

// Move into review state
this.setState(ClientToolCallState.review, { result })
Expand Down

This file was deleted.

Loading