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
Lint
  • Loading branch information
Sg312 committed Aug 6, 2025
commit 2eccae4f92bc936e3f67ac4fdca98d68a9dcc7b1
8 changes: 7 additions & 1 deletion apps/sim/app/api/copilot/methods/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,13 @@ async function pollRedisForTool(
*/
async function interruptHandler(
toolCallId: string
): Promise<{ approved: boolean; rejected: boolean; error?: boolean; message?: string; fullData?: any }> {
): 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 Down
73 changes: 39 additions & 34 deletions apps/sim/lib/copilot/tools/client-tools/get-user-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import type {
ToolMetadata,
} from '@/lib/copilot/tools/types'
import { createLogger } from '@/lib/logs/console/logger'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'

interface GetUserWorkflowParams {
Expand All @@ -21,7 +21,7 @@ interface GetUserWorkflowParams {

export class GetUserWorkflowTool extends BaseTool {
static readonly id = 'get_user_workflow'

metadata: ToolMetadata = {
id: GetUserWorkflowTool.id,
displayConfig: {
Expand Down Expand Up @@ -60,7 +60,8 @@ export class GetUserWorkflowTool extends BaseTool {
properties: {
workflowId: {
type: 'string',
description: 'The ID of the workflow to fetch (optional, uses active workflow if not provided)',
description:
'The ID of the workflow to fetch (optional, uses active workflow if not provided)',
},
includeMetadata: {
type: 'boolean',
Expand All @@ -86,17 +87,17 @@ export class GetUserWorkflowTool extends BaseTool {
options?: ToolExecutionOptions
): Promise<ToolExecuteResult> {
const logger = createLogger('GetUserWorkflowTool')

logger.info('Starting client tool execution', {
toolCallId: toolCall.id,
toolName: toolCall.name,
})

try {
// Parse parameters
const rawParams = toolCall.parameters || toolCall.input || {}
const params = rawParams as GetUserWorkflowParams

// Get workflow ID - use provided or active workflow
let workflowId = params.workflowId
if (!workflowId) {
Expand All @@ -110,12 +111,15 @@ export class GetUserWorkflowTool extends BaseTool {
}
workflowId = activeWorkflowId
}

logger.info('Fetching user workflow from stores', { workflowId, includeMetadata: params.includeMetadata })


logger.info('Fetching user workflow from stores', {
workflowId,
includeMetadata: params.includeMetadata,
})

// Try to get workflow from diff/preview store first, then main store
let workflowState: any = null
Comment thread
Sg312 marked this conversation as resolved.

// Check diff store first
const diffStore = useWorkflowDiffStore.getState()
if (diffStore.diffWorkflow && Object.keys(diffStore.diffWorkflow.blocks || {}).length > 0) {
Expand All @@ -125,27 +129,27 @@ export class GetUserWorkflowTool extends BaseTool {
// Get the actual workflow state from the workflow store
const workflowStore = useWorkflowStore.getState()
const fullWorkflowState = workflowStore.getWorkflowState()

if (!fullWorkflowState || !fullWorkflowState.blocks) {
// Fallback to workflow registry metadata if no workflow state
const workflowRegistry = useWorkflowRegistry.getState()
const workflow = workflowRegistry.workflows[workflowId]

if (!workflow) {
options?.onStateChange?.('errored')
return {
success: false,
error: `Workflow ${workflowId} not found in any store`,
}
}

logger.warn('No workflow state found, using workflow metadata only', { workflowId })
workflowState = workflow
} else {
workflowState = fullWorkflowState
logger.info('Using workflow state from workflow store', {
logger.info('Using workflow state from workflow store', {
workflowId,
blockCount: Object.keys(fullWorkflowState.blocks || {}).length
blockCount: Object.keys(fullWorkflowState.blocks || {}).length,
})
}
}
Expand All @@ -165,43 +169,44 @@ export class GetUserWorkflowTool extends BaseTool {
workflowState.blocks = {}
}
}
logger.info('Validating workflow state', {

logger.info('Validating workflow state', {
workflowId,
hasWorkflowState: !!workflowState,
hasBlocks: !!(workflowState && workflowState.blocks),
hasBlocks: !!workflowState?.blocks,
workflowStateType: typeof workflowState,
})

if (!workflowState || !workflowState.blocks) {
logger.error('Workflow state validation failed', {
workflowId,
workflowState: workflowState,
hasBlocks: !!(workflowState && workflowState.blocks),
hasBlocks: !!workflowState?.blocks,
})
options?.onStateChange?.('errored')
return {
success: false,
error: 'Workflow state is empty or invalid',
}
}

// Include metadata if requested and available
if (params.includeMetadata && workflowState.metadata) {
// Metadata is already included in the workflow state
}
Comment thread
Sg312 marked this conversation as resolved.

logger.info('Successfully fetched user workflow from stores', {
workflowId,
blockCount: Object.keys(workflowState.blocks || {}).length,
fromDiffStore: !!diffStore.diffWorkflow && Object.keys(diffStore.diffWorkflow.blocks || {}).length > 0,
fromDiffStore:
!!diffStore.diffWorkflow && Object.keys(diffStore.diffWorkflow.blocks || {}).length > 0,
})

logger.info('About to stringify workflow state', {
workflowId,
workflowStateKeys: Object.keys(workflowState),
})

// Convert workflow state to JSON string
let workflowJson: string
try {
Expand All @@ -226,25 +231,25 @@ export class GetUserWorkflowTool extends BaseTool {
toolCallId: toolCall.id,
dataLength: workflowJson.length,
})

// Notify server of success with structured data containing userWorkflow
const structuredData = JSON.stringify({
userWorkflow: workflowJson,
})

logger.info('Calling notify with structured data', {
toolCallId: toolCall.id,
structuredDataLength: structuredData.length,
})

await this.notify(toolCall.id, 'success', structuredData)

logger.info('Successfully notified server of success', {
toolCallId: toolCall.id,
})

options?.onStateChange?.('success')

return {
success: true,
data: workflowJson, // Return the same data that goes to Redis
Expand All @@ -256,7 +261,7 @@ export class GetUserWorkflowTool extends BaseTool {
stack: error instanceof Error ? error.stack : undefined,
message: error instanceof Error ? error.message : String(error),
})

try {
// Notify server of error
await this.notify(toolCall.id, 'errored', error.message || 'Failed to fetch workflow')
Expand All @@ -269,13 +274,13 @@ export class GetUserWorkflowTool extends BaseTool {
notifyError: notifyError,
})
}

options?.onStateChange?.('errored')

return {
success: false,
error: error.message || 'Failed to fetch workflow',
}
}
}
}
}
2 changes: 1 addition & 1 deletion apps/sim/lib/copilot/tools/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
* It also provides metadata for server-side tools for display purposes
*/

import { RunWorkflowTool } from '@/lib/copilot/tools/client-tools/run-workflow'
import { GetUserWorkflowTool } from '@/lib/copilot/tools/client-tools/get-user-workflow'
import { RunWorkflowTool } from '@/lib/copilot/tools/client-tools/run-workflow'
import { SERVER_TOOL_METADATA } from '@/lib/copilot/tools/server-tools/definitions'
import type { Tool, ToolMetadata } from '@/lib/copilot/tools/types'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,11 +273,11 @@ async function applyOperationsToYaml(
return yamlDump(workflowData)
}

import { BaseCopilotTool } from '../base'
import { eq } from 'drizzle-orm'
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
import { db } from '@/db'
import { workflow as workflowTable } from '@/db/schema'
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
import { BaseCopilotTool } from '../base'

interface EditWorkflowParams {
operations: EditWorkflowOperation[]
Expand Down Expand Up @@ -386,7 +386,7 @@ async function editWorkflow(params: EditWorkflowParams): Promise<EditWorkflowRes

// Get current workflow state - use provided currentUserWorkflow or fetch from DB
let workflowStateJson: string

if (currentUserWorkflow) {
logger.info('Using provided currentUserWorkflow for edits', {
workflowId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ class GetUserWorkflowTool extends BaseCopilotTool<GetUserWorkflowParams, string>

protected async executeImpl(params: GetUserWorkflowParams): Promise<string> {
const logger = createLogger('GetUserWorkflow')

logger.info('Server tool received params', {
hasFullData: !!params.fullData,
hasConfirmationMessage: !!params.confirmationMessage,
fullDataType: typeof params.fullData,
fullDataKeys: params.fullData ? Object.keys(params.fullData) : null,
confirmationMessageLength: params.confirmationMessage?.length || 0,
})

// Extract the workflow data from fullData or confirmationMessage
let workflowData: string | null = null
if (params.fullData && params.fullData.userWorkflow) {

if (params.fullData?.userWorkflow) {
// New format: fullData contains structured data with userWorkflow field
workflowData = params.fullData.userWorkflow
logger.info('Using workflow data from fullData.userWorkflow', {
Expand All @@ -39,15 +39,15 @@ class GetUserWorkflowTool extends BaseCopilotTool<GetUserWorkflowParams, string>
messageLength: params.confirmationMessage.length,
messagePreview: params.confirmationMessage.substring(0, 100),
})

try {
// Try to parse the confirmation message as structured data
const parsedMessage = JSON.parse(params.confirmationMessage)
if (parsedMessage && parsedMessage.userWorkflow) {
workflowData = parsedMessage.userWorkflow
logger.info('Successfully extracted userWorkflow from confirmationMessage', {
dataLength: workflowData?.length || 0,
})
if (parsedMessage?.userWorkflow) {
workflowData = parsedMessage.userWorkflow
logger.info('Successfully extracted userWorkflow from confirmationMessage', {
dataLength: workflowData?.length || 0,
})
} else {
// Fallback: treat the entire message as workflow data
workflowData = params.confirmationMessage
Expand All @@ -74,7 +74,7 @@ class GetUserWorkflowTool extends BaseCopilotTool<GetUserWorkflowParams, string>
try {
// Parse the workflow data to validate it's valid JSON
const workflowState = JSON.parse(workflowData)

if (!workflowState || !workflowState.blocks) {
throw new Error('Invalid workflow state received from client tool')
}
Expand All @@ -94,5 +94,3 @@ class GetUserWorkflowTool extends BaseCopilotTool<GetUserWorkflowParams, string>

// Export the tool instance
export const getUserWorkflowTool = new GetUserWorkflowTool()


16 changes: 8 additions & 8 deletions apps/sim/stores/copilot/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,26 +543,26 @@ function createToolCall(id: string, name: string, input: any = {}): any {
onStateChange: (state: any) => {
// Update the tool call state in the store
const currentState = useCopilotStore.getState()
const updatedMessages = currentState.messages.map(msg => ({
const updatedMessages = currentState.messages.map((msg) => ({
...msg,
toolCalls: msg.toolCalls?.map(tc =>
toolCalls: msg.toolCalls?.map((tc) =>
tc.id === toolCall.id ? { ...tc, state } : tc
),
contentBlocks: msg.contentBlocks?.map(block =>
block.type === 'tool_call' && block.toolCall?.id === toolCall.id
contentBlocks: msg.contentBlocks?.map((block) =>
block.type === 'tool_call' && block.toolCall?.id === toolCall.id
? { ...block, toolCall: { ...block.toolCall, state } }
: block
)
),
}))

useCopilotStore.setState({ messages: updatedMessages })
},
})
}
} catch (error) {
logger.error('Error auto-executing client tool:', name, toolCall.id, error)
setToolCallState(toolCall, 'errored', {
error: error instanceof Error ? error.message : 'Auto-execution failed'
setToolCallState(toolCall, 'errored', {
error: error instanceof Error ? error.message : 'Auto-execution failed',
})
}
}, 0)
Comment thread
Sg312 marked this conversation as resolved.
Expand Down