Skip to content

Commit f94258e

Browse files
authored
improvement(copilot): incremental edits (#891)
* v1 * Incremental edits * Lint * Remove dev env * Fix tests * Lint
1 parent 05e689b commit f94258e

File tree

10 files changed

+572
-114
lines changed

10 files changed

+572
-114
lines changed

apps/sim/app/api/copilot/methods/route.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,14 @@ describe('Copilot Methods API Route', () => {
354354
86400
355355
)
356356
expect(mockRedisGet).toHaveBeenCalledWith('tool_call:tool-call-123')
357-
expect(mockToolRegistryExecute).toHaveBeenCalledWith('interrupt-tool', { key: 'value' })
357+
expect(mockToolRegistryExecute).toHaveBeenCalledWith('interrupt-tool', {
358+
key: 'value',
359+
confirmationMessage: 'User approved',
360+
fullData: {
361+
message: 'User approved',
362+
status: 'accepted',
363+
},
364+
})
358365
})
359366

360367
it('should handle tool execution with interrupt - user rejection', async () => {
@@ -613,6 +620,10 @@ describe('Copilot Methods API Route', () => {
613620
expect(mockToolRegistryExecute).toHaveBeenCalledWith('no_op', {
614621
existing: 'param',
615622
confirmationMessage: 'Confirmation message',
623+
fullData: {
624+
message: 'Confirmation message',
625+
status: 'accepted',
626+
},
616627
})
617628
})
618629

apps/sim/app/api/copilot/methods/route.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ async function addToolToRedis(toolCallId: string): Promise<void> {
5757
*/
5858
async function pollRedisForTool(
5959
toolCallId: string
60-
): Promise<{ status: NotificationStatus; message?: string } | null> {
60+
): Promise<{ status: NotificationStatus; message?: string; fullData?: any } | null> {
6161
const redis = getRedisClient()
6262
if (!redis) {
6363
logger.warn('pollRedisForTool: Redis client not available')
@@ -86,12 +86,14 @@ async function pollRedisForTool(
8686

8787
let status: NotificationStatus | null = null
8888
let message: string | undefined
89+
let fullData: any = null
8990

9091
// Try to parse as JSON (new format), fallback to string (old format)
9192
try {
9293
const parsedData = JSON.parse(redisValue)
9394
status = parsedData.status as NotificationStatus
9495
message = parsedData.message || undefined
96+
fullData = parsedData // Store the full parsed data
9597
} catch {
9698
// Fallback to old format (direct status string)
9799
status = redisValue as NotificationStatus
@@ -138,7 +140,7 @@ async function pollRedisForTool(
138140
})
139141
}
140142

141-
return { status, message }
143+
return { status, message, fullData }
142144
}
143145

144146
// Wait before next poll
@@ -163,9 +165,13 @@ async function pollRedisForTool(
163165
* Handle tool calls that require user interruption/approval
164166
* Returns { approved: boolean, rejected: boolean, error?: boolean, message?: string } to distinguish between rejection, timeout, and error
165167
*/
166-
async function interruptHandler(
167-
toolCallId: string
168-
): Promise<{ approved: boolean; rejected: boolean; error?: boolean; message?: string }> {
168+
async function interruptHandler(toolCallId: string): Promise<{
169+
approved: boolean
170+
rejected: boolean
171+
error?: boolean
172+
message?: string
173+
fullData?: any
174+
}> {
169175
if (!toolCallId) {
170176
logger.error('interruptHandler: No tool call ID provided')
171177
return { approved: false, rejected: false, error: true, message: 'No tool call ID provided' }
@@ -185,31 +191,31 @@ async function interruptHandler(
185191
return { approved: false, rejected: false }
186192
}
187193

188-
const { status, message } = result
194+
const { status, message, fullData } = result
189195

190196
if (status === 'rejected') {
191197
logger.info('Tool execution rejected by user', { toolCallId, message })
192-
return { approved: false, rejected: true, message }
198+
return { approved: false, rejected: true, message, fullData }
193199
}
194200

195201
if (status === 'accepted') {
196202
logger.info('Tool execution approved by user', { toolCallId, message })
197-
return { approved: true, rejected: false, message }
203+
return { approved: true, rejected: false, message, fullData }
198204
}
199205

200206
if (status === 'error') {
201207
logger.error('Tool execution failed with error', { toolCallId, message })
202-
return { approved: false, rejected: false, error: true, message }
208+
return { approved: false, rejected: false, error: true, message, fullData }
203209
}
204210

205211
if (status === 'background') {
206212
logger.info('Tool execution moved to background', { toolCallId, message })
207-
return { approved: true, rejected: false, message }
213+
return { approved: true, rejected: false, message, fullData }
208214
}
209215

210216
if (status === 'success') {
211217
logger.info('Tool execution completed successfully', { toolCallId, message })
212-
return { approved: true, rejected: false, message }
218+
return { approved: true, rejected: false, message, fullData }
213219
}
214220

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

328334
// Handle interrupt flow
329-
const { approved, rejected, error, message } = await interruptHandler(toolCallId)
335+
const { approved, rejected, error, message, fullData } = await interruptHandler(toolCallId)
330336

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

374-
// For noop tool, pass the confirmation message as a parameter
375-
if (methodId === 'no_op' && message) {
380+
// For tools that need confirmation data, pass the message and/or fullData as parameters
381+
if (message) {
376382
params.confirmationMessage = message
377383
}
384+
if (fullData) {
385+
params.fullData = fullData
386+
}
378387
}
379388

380389
// Execute the tool directly via registry

apps/sim/app/api/yaml/diff/create/route.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ export async function POST(request: NextRequest) {
7070
// Note: This endpoint is stateless, so we need to get this from the request
7171
const currentWorkflowState = (body as any).currentWorkflowState
7272

73+
// Ensure currentWorkflowState has all required properties with proper defaults if provided
74+
if (currentWorkflowState) {
75+
if (!currentWorkflowState.loops) {
76+
currentWorkflowState.loops = {}
77+
}
78+
if (!currentWorkflowState.parallels) {
79+
currentWorkflowState.parallels = {}
80+
}
81+
}
82+
7383
logger.info(`[${requestId}] Creating diff from YAML`, {
7484
contentLength: yamlContent.length,
7585
hasDiffAnalysis: !!diffAnalysis,

apps/sim/app/api/yaml/diff/merge/route.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ const MergeDiffRequestSchema = z.object({
2424
proposedState: z.object({
2525
blocks: z.record(z.any()),
2626
edges: z.array(z.any()),
27-
loops: z.record(z.any()),
28-
parallels: z.record(z.any()),
27+
loops: z.record(z.any()).optional(),
28+
parallels: z.record(z.any()).optional(),
2929
}),
3030
diffAnalysis: z.any().optional(),
3131
metadata: z.object({
@@ -50,6 +50,14 @@ export async function POST(request: NextRequest) {
5050
const body = await request.json()
5151
const { existingDiff, yamlContent, diffAnalysis, options } = MergeDiffRequestSchema.parse(body)
5252

53+
// Ensure existingDiff.proposedState has all required properties with proper defaults
54+
if (!existingDiff.proposedState.loops) {
55+
existingDiff.proposedState.loops = {}
56+
}
57+
if (!existingDiff.proposedState.parallels) {
58+
existingDiff.proposedState.parallels = {}
59+
}
60+
5361
logger.info(`[${requestId}] Merging diff from YAML`, {
5462
contentLength: yamlContent.length,
5563
existingBlockCount: Object.keys(existingDiff.proposedState.blocks).length,

0 commit comments

Comments
 (0)