Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion apps/sim/app/api/copilot/methods/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,14 @@ describe('Copilot Methods API Route', () => {
86400
)
expect(mockRedisGet).toHaveBeenCalledWith('tool_call:tool-call-123')
expect(mockToolRegistryExecute).toHaveBeenCalledWith('interrupt-tool', { key: 'value' })
expect(mockToolRegistryExecute).toHaveBeenCalledWith('interrupt-tool', {
key: 'value',
confirmationMessage: 'User approved',
fullData: {
message: 'User approved',
status: 'accepted',
},
})
})

it('should handle tool execution with interrupt - user rejection', async () => {
Expand Down Expand Up @@ -613,6 +620,10 @@ describe('Copilot Methods API Route', () => {
expect(mockToolRegistryExecute).toHaveBeenCalledWith('no_op', {
existing: 'param',
confirmationMessage: 'Confirmation message',
fullData: {
message: 'Confirmation message',
status: 'accepted',
},
})
})

Expand Down
37 changes: 23 additions & 14 deletions apps/sim/app/api/copilot/methods/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ async function addToolToRedis(toolCallId: string): Promise<void> {
*/
async function pollRedisForTool(
toolCallId: string
): Promise<{ status: NotificationStatus; message?: string } | null> {
): Promise<{ status: NotificationStatus; message?: string; fullData?: any } | null> {
const redis = getRedisClient()
if (!redis) {
logger.warn('pollRedisForTool: Redis client not available')
Expand Down Expand Up @@ -86,12 +86,14 @@ async function pollRedisForTool(

let status: NotificationStatus | null = null
let message: string | undefined
let fullData: any = null

// Try to parse as JSON (new format), fallback to string (old format)
try {
const parsedData = JSON.parse(redisValue)
status = parsedData.status as NotificationStatus
message = parsedData.message || undefined
fullData = parsedData // Store the full parsed data
} catch {
// Fallback to old format (direct status string)
status = redisValue as NotificationStatus
Expand Down Expand Up @@ -138,7 +140,7 @@ async function pollRedisForTool(
})
}

return { status, message }
return { status, message, fullData }
}

// Wait before next poll
Expand All @@ -163,9 +165,13 @@ async function pollRedisForTool(
* Handle tool calls that require user interruption/approval
* Returns { approved: boolean, rejected: boolean, error?: boolean, message?: string } to distinguish between rejection, timeout, and error
*/
async function interruptHandler(
toolCallId: string
): Promise<{ approved: boolean; rejected: boolean; error?: boolean; message?: string }> {
async function interruptHandler(toolCallId: string): Promise<{
approved: boolean
rejected: boolean
error?: boolean
message?: string
fullData?: any
}> {
if (!toolCallId) {
logger.error('interruptHandler: No tool call ID provided')
return { approved: false, rejected: false, error: true, message: 'No tool call ID provided' }
Expand All @@ -185,31 +191,31 @@ async function interruptHandler(
return { approved: false, rejected: false }
}

const { status, message } = result
const { status, message, fullData } = result

if (status === 'rejected') {
logger.info('Tool execution rejected by user', { toolCallId, message })
return { approved: false, rejected: true, message }
return { approved: false, rejected: true, message, fullData }
}

if (status === 'accepted') {
logger.info('Tool execution approved by user', { toolCallId, message })
return { approved: true, rejected: false, message }
return { approved: true, rejected: false, message, fullData }
}

if (status === 'error') {
logger.error('Tool execution failed with error', { toolCallId, message })
return { approved: false, rejected: false, error: true, message }
return { approved: false, rejected: false, error: true, message, fullData }
}

if (status === 'background') {
logger.info('Tool execution moved to background', { toolCallId, message })
return { approved: true, rejected: false, message }
return { approved: true, rejected: false, message, fullData }
}

if (status === 'success') {
logger.info('Tool execution completed successfully', { toolCallId, message })
return { approved: true, rejected: false, message }
return { approved: true, rejected: false, message, fullData }
}

logger.warn('Unexpected tool call status', { toolCallId, status, message })
Expand Down Expand Up @@ -326,7 +332,7 @@ export async function POST(req: NextRequest) {
})

// Handle interrupt flow
const { approved, rejected, error, message } = await interruptHandler(toolCallId)
const { approved, rejected, error, message, fullData } = await interruptHandler(toolCallId)

if (rejected) {
logger.info(`[${requestId}] Tool execution rejected by user`, {
Expand Down Expand Up @@ -371,10 +377,13 @@ export async function POST(req: NextRequest) {
message,
})

// For noop tool, pass the confirmation message as a parameter
if (methodId === 'no_op' && message) {
// For tools that need confirmation data, pass the message and/or fullData as parameters
if (message) {
params.confirmationMessage = message
}
if (fullData) {
params.fullData = fullData
}
}

// Execute the tool directly via registry
Expand Down
10 changes: 10 additions & 0 deletions apps/sim/app/api/yaml/diff/create/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ export async function POST(request: NextRequest) {
// Note: This endpoint is stateless, so we need to get this from the request
const currentWorkflowState = (body as any).currentWorkflowState

// Ensure currentWorkflowState has all required properties with proper defaults if provided
if (currentWorkflowState) {
if (!currentWorkflowState.loops) {
currentWorkflowState.loops = {}
}
if (!currentWorkflowState.parallels) {
currentWorkflowState.parallels = {}
}
}

logger.info(`[${requestId}] Creating diff from YAML`, {
contentLength: yamlContent.length,
hasDiffAnalysis: !!diffAnalysis,
Expand Down
12 changes: 10 additions & 2 deletions apps/sim/app/api/yaml/diff/merge/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const MergeDiffRequestSchema = z.object({
proposedState: z.object({
blocks: z.record(z.any()),
edges: z.array(z.any()),
loops: z.record(z.any()),
parallels: z.record(z.any()),
loops: z.record(z.any()).optional(),
parallels: z.record(z.any()).optional(),
}),
diffAnalysis: z.any().optional(),
metadata: z.object({
Expand All @@ -50,6 +50,14 @@ export async function POST(request: NextRequest) {
const body = await request.json()
const { existingDiff, yamlContent, diffAnalysis, options } = MergeDiffRequestSchema.parse(body)

// Ensure existingDiff.proposedState has all required properties with proper defaults
if (!existingDiff.proposedState.loops) {
existingDiff.proposedState.loops = {}
}
if (!existingDiff.proposedState.parallels) {
existingDiff.proposedState.parallels = {}
}

logger.info(`[${requestId}] Merging diff from YAML`, {
contentLength: yamlContent.length,
existingBlockCount: Object.keys(existingDiff.proposedState.blocks).length,
Expand Down
Loading