From 8b87a07508d1d9398a8de9888643923f97fb2a10 Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Wed, 4 Feb 2026 12:35:12 -0800 Subject: [PATCH] Fix lint --- apps/sim/app/api/copilot/chat/route.ts | 13 +-- apps/sim/app/api/copilot/chat/stream/route.ts | 11 +- apps/sim/app/api/mcp/copilot/route.ts | 6 +- apps/sim/app/api/v1/copilot/chat/route.ts | 21 +++- .../components/tool-call/tool-call.tsx | 7 +- apps/sim/lib/copilot/orchestrator/config.ts | 1 - apps/sim/lib/copilot/orchestrator/index.ts | 13 ++- .../lib/copilot/orchestrator/persistence.ts | 6 +- .../lib/copilot/orchestrator/sse-handlers.ts | 66 ++++++----- .../lib/copilot/orchestrator/sse-parser.ts | 1 - .../lib/copilot/orchestrator/stream-buffer.ts | 6 +- apps/sim/lib/copilot/orchestrator/subagent.ts | 11 +- .../lib/copilot/orchestrator/tool-executor.ts | 107 ++++++++++-------- apps/sim/lib/copilot/orchestrator/types.ts | 1 - apps/sim/lib/copilot/tools/mcp/definitions.ts | 24 ++-- .../tools/server/workflow/edit-workflow.ts | 7 +- .../copilot/tools/shared/workflow-utils.ts | 5 +- apps/sim/lib/workflows/utils.ts | 5 +- apps/sim/stores/panel/copilot/store.ts | 14 ++- 19 files changed, 187 insertions(+), 138 deletions(-) diff --git a/apps/sim/app/api/copilot/chat/route.ts b/apps/sim/app/api/copilot/chat/route.ts index 2188f80d3..c6b1250c5 100644 --- a/apps/sim/app/api/copilot/chat/route.ts +++ b/apps/sim/app/api/copilot/chat/route.ts @@ -9,6 +9,12 @@ import { generateChatTitle } from '@/lib/copilot/chat-title' import { getCopilotModel } from '@/lib/copilot/config' import { SIM_AGENT_VERSION } from '@/lib/copilot/constants' import { COPILOT_MODEL_IDS, COPILOT_REQUEST_MODES } from '@/lib/copilot/models' +import { orchestrateCopilotStream } from '@/lib/copilot/orchestrator' +import { + createStreamEventWriter, + resetStreamBuffer, + setStreamMeta, +} from '@/lib/copilot/orchestrator/stream-buffer' import { authenticateCopilotRequestSessionOnly, createBadRequestResponse, @@ -24,16 +30,9 @@ import { createFileContent } from '@/lib/uploads/utils/file-utils' import { resolveWorkflowIdForUser } from '@/lib/workflows/utils' import { tools } from '@/tools/registry' import { getLatestVersionTools, stripVersionSuffix } from '@/tools/utils' -import { orchestrateCopilotStream } from '@/lib/copilot/orchestrator' -import { - createStreamEventWriter, - resetStreamBuffer, - setStreamMeta, -} from '@/lib/copilot/orchestrator/stream-buffer' const logger = createLogger('CopilotChatAPI') - const FileAttachmentSchema = z.object({ id: z.string(), key: z.string(), diff --git a/apps/sim/app/api/copilot/chat/stream/route.ts b/apps/sim/app/api/copilot/chat/stream/route.ts index e04271171..c1fd6fb22 100644 --- a/apps/sim/app/api/copilot/chat/stream/route.ts +++ b/apps/sim/app/api/copilot/chat/stream/route.ts @@ -1,12 +1,12 @@ -import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@sim/logger' -import { SSE_HEADERS } from '@/lib/core/utils/sse' -import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request-helpers' +import { type NextRequest, NextResponse } from 'next/server' import { getStreamMeta, readStreamEvents, type StreamMeta, } from '@/lib/copilot/orchestrator/stream-buffer' +import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request-helpers' +import { SSE_HEADERS } from '@/lib/core/utils/sse' const logger = createLogger('CopilotChatStreamAPI') const POLL_INTERVAL_MS = 250 @@ -56,9 +56,7 @@ export async function GET(request: NextRequest) { // Batch mode: return all buffered events as JSON if (batchMode) { const events = await readStreamEvents(streamId, fromEventId) - const filteredEvents = toEventId - ? events.filter((e) => e.eventId <= toEventId) - : events + const filteredEvents = toEventId ? events.filter((e) => e.eventId <= toEventId) : events logger.info('[Resume] Batch response', { streamId, fromEventId, @@ -130,4 +128,3 @@ export async function GET(request: NextRequest) { return new Response(stream, { headers: SSE_HEADERS }) } - diff --git a/apps/sim/app/api/mcp/copilot/route.ts b/apps/sim/app/api/mcp/copilot/route.ts index b797ccca5..ee297b5b9 100644 --- a/apps/sim/app/api/mcp/copilot/route.ts +++ b/apps/sim/app/api/mcp/copilot/route.ts @@ -15,8 +15,11 @@ import { type NextRequest, NextResponse } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { getCopilotModel } from '@/lib/copilot/config' import { orchestrateSubagentStream } from '@/lib/copilot/orchestrator/subagent' +import { + executeToolServerSide, + prepareExecutionContext, +} from '@/lib/copilot/orchestrator/tool-executor' import { DIRECT_TOOL_DEFS, SUBAGENT_TOOL_DEFS } from '@/lib/copilot/tools/mcp/definitions' -import { executeToolServerSide, prepareExecutionContext } from '@/lib/copilot/orchestrator/tool-executor' const logger = createLogger('CopilotMcpAPI') @@ -408,4 +411,3 @@ async function handleSubagentToolCall( return NextResponse.json(createResponse(id, response)) } - diff --git a/apps/sim/app/api/v1/copilot/chat/route.ts b/apps/sim/app/api/v1/copilot/chat/route.ts index 517959c44..57def1fb5 100644 --- a/apps/sim/app/api/v1/copilot/chat/route.ts +++ b/apps/sim/app/api/v1/copilot/chat/route.ts @@ -1,12 +1,12 @@ import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' -import { authenticateV1Request } from '@/app/api/v1/auth' import { getCopilotModel } from '@/lib/copilot/config' import { SIM_AGENT_VERSION } from '@/lib/copilot/constants' import { COPILOT_REQUEST_MODES } from '@/lib/copilot/models' import { orchestrateCopilotStream } from '@/lib/copilot/orchestrator' import { resolveWorkflowIdForUser } from '@/lib/workflows/utils' +import { authenticateV1Request } from '@/app/api/v1/auth' const logger = createLogger('CopilotHeadlessAPI') @@ -24,7 +24,7 @@ const RequestSchema = z.object({ /** * POST /api/v1/copilot/chat * Headless copilot endpoint for server-side orchestration. - * + * * workflowId is optional - if not provided: * - If workflowName is provided, finds that workflow * - Otherwise uses the user's first workflow as context @@ -33,7 +33,10 @@ const RequestSchema = z.object({ export async function POST(req: NextRequest) { const auth = await authenticateV1Request(req) if (!auth.authenticated || !auth.userId) { - return NextResponse.json({ success: false, error: auth.error || 'Unauthorized' }, { status: 401 }) + return NextResponse.json( + { success: false, error: auth.error || 'Unauthorized' }, + { status: 401 } + ) } try { @@ -43,10 +46,17 @@ export async function POST(req: NextRequest) { const selectedModel = parsed.model || defaults.model // Resolve workflow ID - const resolved = await resolveWorkflowIdForUser(auth.userId, parsed.workflowId, parsed.workflowName) + const resolved = await resolveWorkflowIdForUser( + auth.userId, + parsed.workflowId, + parsed.workflowName + ) if (!resolved) { return NextResponse.json( - { success: false, error: 'No workflows found. Create a workflow first or provide a valid workflowId.' }, + { + success: false, + error: 'No workflows found. Create a workflow first or provide a valid workflowId.', + }, { status: 400 } ) } @@ -104,4 +114,3 @@ export async function POST(req: NextRequest) { return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 }) } } - diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx index 9d575cfd5..8d0e59eff 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx @@ -1,7 +1,7 @@ 'use client' -import { createLogger } from '@sim/logger' import { memo, useEffect, useMemo, useRef, useState } from 'react' +import { createLogger } from '@sim/logger' import clsx from 'clsx' import { ChevronUp, LayoutList } from 'lucide-react' import Editor from 'react-simple-code-editor' @@ -1260,7 +1260,10 @@ function shouldShowRunSkipButtons(toolCall: CopilotToolCall): boolean { const toolCallLogger = createLogger('CopilotToolCall') -async function sendToolDecision(toolCallId: string, status: 'accepted' | 'rejected' | 'background') { +async function sendToolDecision( + toolCallId: string, + status: 'accepted' | 'rejected' | 'background' +) { try { await fetch('/api/copilot/confirm', { method: 'POST', diff --git a/apps/sim/lib/copilot/orchestrator/config.ts b/apps/sim/lib/copilot/orchestrator/config.ts index 80c22d436..9e2dc7221 100644 --- a/apps/sim/lib/copilot/orchestrator/config.ts +++ b/apps/sim/lib/copilot/orchestrator/config.ts @@ -34,4 +34,3 @@ export const SUBAGENT_TOOL_NAMES = [ ] as const export const SUBAGENT_TOOL_SET = new Set(SUBAGENT_TOOL_NAMES) - diff --git a/apps/sim/lib/copilot/orchestrator/index.ts b/apps/sim/lib/copilot/orchestrator/index.ts index f2286de75..e990612be 100644 --- a/apps/sim/lib/copilot/orchestrator/index.ts +++ b/apps/sim/lib/copilot/orchestrator/index.ts @@ -1,7 +1,5 @@ import { createLogger } from '@sim/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/copilot/constants' -import { env } from '@/lib/core/config/env' -import { parseSSEStream } from '@/lib/copilot/orchestrator/sse-parser' import { getToolCallIdFromEvent, handleSubagentRouting, @@ -13,15 +11,16 @@ import { wasToolCallSeen, wasToolResultSeen, } from '@/lib/copilot/orchestrator/sse-handlers' +import { parseSSEStream } from '@/lib/copilot/orchestrator/sse-parser' import { prepareExecutionContext } from '@/lib/copilot/orchestrator/tool-executor' import type { - ExecutionContext, OrchestratorOptions, OrchestratorResult, SSEEvent, StreamingContext, ToolCallSummary, } from '@/lib/copilot/orchestrator/types' +import { env } from '@/lib/core/config/env' const logger = createLogger('CopilotOrchestrator') const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT @@ -73,7 +72,9 @@ export async function orchestrateCopilotStream( if (!response.ok) { const errorText = await response.text().catch(() => '') - throw new Error(`Copilot backend error (${response.status}): ${errorText || response.statusText}`) + throw new Error( + `Copilot backend error (${response.status}): ${errorText || response.statusText}` + ) } if (!response.body) { @@ -104,7 +105,8 @@ export async function orchestrateCopilotStream( const toolCallId = getToolCallIdFromEvent(normalizedEvent) const eventData = normalizedEvent.data - const isPartialToolCall = normalizedEvent.type === 'tool_call' && eventData?.partial === true + const isPartialToolCall = + normalizedEvent.type === 'tool_call' && eventData?.partial === true const shouldSkipToolCall = normalizedEvent.type === 'tool_call' && @@ -220,4 +222,3 @@ function buildResult(context: StreamingContext): OrchestratorResult { errors: context.errors.length ? context.errors : undefined, } } - diff --git a/apps/sim/lib/copilot/orchestrator/persistence.ts b/apps/sim/lib/copilot/orchestrator/persistence.ts index d7b015f00..418b652a5 100644 --- a/apps/sim/lib/copilot/orchestrator/persistence.ts +++ b/apps/sim/lib/copilot/orchestrator/persistence.ts @@ -69,7 +69,10 @@ export async function saveMessages( /** * Update the conversationId for a chat without overwriting messages. */ -export async function updateChatConversationId(chatId: string, conversationId: string): Promise { +export async function updateChatConversationId( + chatId: string, + conversationId: string +): Promise { await db .update(copilotChats) .set({ @@ -135,4 +138,3 @@ export async function getToolConfirmation(toolCallId: string): Promise<{ return null } } - diff --git a/apps/sim/lib/copilot/orchestrator/sse-handlers.ts b/apps/sim/lib/copilot/orchestrator/sse-handlers.ts index 4ad167345..9a3d0f1b9 100644 --- a/apps/sim/lib/copilot/orchestrator/sse-handlers.ts +++ b/apps/sim/lib/copilot/orchestrator/sse-handlers.ts @@ -1,4 +1,7 @@ import { createLogger } from '@sim/logger' +import { INTERRUPT_TOOL_SET, SUBAGENT_TOOL_SET } from '@/lib/copilot/orchestrator/config' +import { getToolConfirmation } from '@/lib/copilot/orchestrator/persistence' +import { executeToolServerSide, markToolComplete } from '@/lib/copilot/orchestrator/tool-executor' import type { ContentBlock, ExecutionContext, @@ -7,9 +10,6 @@ import type { StreamingContext, ToolCallState, } from '@/lib/copilot/orchestrator/types' -import { executeToolServerSide, markToolComplete } from '@/lib/copilot/orchestrator/tool-executor' -import { getToolConfirmation } from '@/lib/copilot/orchestrator/persistence' -import { INTERRUPT_TOOL_SET, SUBAGENT_TOOL_SET } from '@/lib/copilot/orchestrator/config' const logger = createLogger('CopilotSseHandlers') @@ -25,9 +25,12 @@ const seenToolResults = new Set() export function markToolCallSeen(toolCallId: string): void { seenToolCalls.add(toolCallId) - setTimeout(() => { - seenToolCalls.delete(toolCallId) - }, 5 * 60 * 1000) + setTimeout( + () => { + seenToolCalls.delete(toolCallId) + }, + 5 * 60 * 1000 + ) } export function wasToolCallSeen(toolCallId: string): boolean { @@ -99,9 +102,12 @@ export function normalizeSseEvent(event: SSEEvent): SSEEvent { */ export function markToolResultSeen(toolCallId: string): void { seenToolResults.add(toolCallId) - setTimeout(() => { - seenToolResults.delete(toolCallId) - }, 5 * 60 * 1000) + setTimeout( + () => { + seenToolResults.delete(toolCallId) + }, + 5 * 60 * 1000 + ) } /** @@ -134,10 +140,7 @@ export type SSEHandler = ( options: OrchestratorOptions ) => void | Promise -function addContentBlock( - context: StreamingContext, - block: Omit -): void { +function addContentBlock(context: StreamingContext, block: Omit): void { context.contentBlocks.push({ ...block, timestamp: Date.now(), @@ -195,7 +198,7 @@ async function executeToolAndReport( success: result.success, result: result.output, data: { - id: toolCall.id, + id: toolCall.id, name: toolCall.name, success: result.success, result: result.output, @@ -256,7 +259,7 @@ export const sseHandlers: Record = { const hasError = !!data?.error || !!data?.result?.error // If explicitly set, use that; otherwise infer from data presence - const success = hasExplicitSuccess ? !!explicitSuccess : (hasResultData && !hasError) + const success = hasExplicitSuccess ? !!explicitSuccess : hasResultData && !hasError current.status = success ? 'success' : 'error' current.endTime = Date.now() @@ -306,7 +309,10 @@ export const sseHandlers: Record = { // If we've already completed this tool call, ignore late/duplicate tool_call events // to avoid resetting UI/state back to pending and re-executing. - if (existing?.endTime || (existing && existing.status !== 'pending' && existing.status !== 'executing')) { + if ( + existing?.endTime || + (existing && existing.status !== 'pending' && existing.status !== 'executing') + ) { if (!existing.params && args) { existing.params = args } @@ -343,7 +349,10 @@ export const sseHandlers: Record = { if (RESPOND_TOOL_SET.has(toolName)) { toolCall.status = 'success' toolCall.endTime = Date.now() - toolCall.result = { success: true, output: 'Internal respond tool - handled by copilot backend' } + toolCall.result = { + success: true, + output: 'Internal respond tool - handled by copilot backend', + } return } @@ -429,12 +438,14 @@ export const sseHandlers: Record = { context.currentThinkingBlock = null return } - const chunk = typeof event.data === 'string' ? event.data : event.data?.data || event.data?.content + const chunk = + typeof event.data === 'string' ? event.data : event.data?.data || event.data?.content if (!chunk || !context.currentThinkingBlock) return context.currentThinkingBlock.content = `${context.currentThinkingBlock.content || ''}${chunk}` }, content: (event, context) => { - const chunk = typeof event.data === 'string' ? event.data : event.data?.content || event.data?.data + const chunk = + typeof event.data === 'string' ? event.data : event.data?.content || event.data?.data if (!chunk) return context.accumulatedContent += chunk addContentBlock(context, { type: 'text', content: chunk }) @@ -452,7 +463,9 @@ export const sseHandlers: Record = { }, error: (event, context) => { const message = - event.data?.message || event.data?.error || (typeof event.data === 'string' ? event.data : null) + event.data?.message || + event.data?.error || + (typeof event.data === 'string' ? event.data : null) if (message) { context.errors.push(message) } @@ -466,7 +479,8 @@ export const subAgentHandlers: Record = { if (!parentToolCallId || !event.data) return const chunk = typeof event.data === 'string' ? event.data : event.data?.content || '' if (!chunk) return - context.subAgentContent[parentToolCallId] = (context.subAgentContent[parentToolCallId] || '') + chunk + context.subAgentContent[parentToolCallId] = + (context.subAgentContent[parentToolCallId] || '') + chunk addContentBlock(context, { type: 'subagent_text', content: chunk }) }, tool_call: async (event, context, execContext, options) => { @@ -510,7 +524,10 @@ export const subAgentHandlers: Record = { if (RESPOND_TOOL_SET.has(toolName)) { toolCall.status = 'success' toolCall.endTime = Date.now() - toolCall.result = { success: true, output: 'Internal respond tool - handled by copilot backend' } + toolCall.result = { + success: true, + output: 'Internal respond tool - handled by copilot backend', + } return } @@ -541,9 +558,7 @@ export const subAgentHandlers: Record = { const status = success ? 'success' : 'error' const endTime = Date.now() - const result = hasResultData - ? { success, output: data?.result || data?.data } - : undefined + const result = hasResultData ? { success, output: data?.result || data?.data } : undefined if (subAgentToolCall) { subAgentToolCall.status = status @@ -572,4 +587,3 @@ export function handleSubagentRouting(event: SSEEvent, context: StreamingContext } return true } - diff --git a/apps/sim/lib/copilot/orchestrator/sse-parser.ts b/apps/sim/lib/copilot/orchestrator/sse-parser.ts index 06873289e..8ab50365c 100644 --- a/apps/sim/lib/copilot/orchestrator/sse-parser.ts +++ b/apps/sim/lib/copilot/orchestrator/sse-parser.ts @@ -69,4 +69,3 @@ export async function* parseSSEStream( } } } - diff --git a/apps/sim/lib/copilot/orchestrator/stream-buffer.ts b/apps/sim/lib/copilot/orchestrator/stream-buffer.ts index 236047841..24621ee57 100644 --- a/apps/sim/lib/copilot/orchestrator/stream-buffer.ts +++ b/apps/sim/lib/copilot/orchestrator/stream-buffer.ts @@ -78,10 +78,7 @@ export async function resetStreamBuffer(streamId: string): Promise { } } -export async function setStreamMeta( - streamId: string, - meta: StreamMeta -): Promise { +export async function setStreamMeta(streamId: string, meta: StreamMeta): Promise { const redis = getRedisClient() if (!redis) return try { @@ -263,4 +260,3 @@ export async function readStreamEvents( return [] } } - diff --git a/apps/sim/lib/copilot/orchestrator/subagent.ts b/apps/sim/lib/copilot/orchestrator/subagent.ts index 1d649eb3c..fa1f3d36a 100644 --- a/apps/sim/lib/copilot/orchestrator/subagent.ts +++ b/apps/sim/lib/copilot/orchestrator/subagent.ts @@ -1,6 +1,5 @@ import { createLogger } from '@sim/logger' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/copilot/constants' -import { parseSSEStream } from '@/lib/copilot/orchestrator/sse-parser' import { getToolCallIdFromEvent, handleSubagentRouting, @@ -12,6 +11,7 @@ import { wasToolCallSeen, wasToolResultSeen, } from '@/lib/copilot/orchestrator/sse-handlers' +import { parseSSEStream } from '@/lib/copilot/orchestrator/sse-parser' import { prepareExecutionContext } from '@/lib/copilot/orchestrator/tool-executor' import type { ExecutionContext, @@ -121,7 +121,8 @@ export async function orchestrateSubagentStream( const toolCallId = getToolCallIdFromEvent(normalizedEvent) const eventData = normalizedEvent.data - const isPartialToolCall = normalizedEvent.type === 'tool_call' && eventData?.partial === true + const isPartialToolCall = + normalizedEvent.type === 'tool_call' && eventData?.partial === true const shouldSkipToolCall = normalizedEvent.type === 'tool_call' && @@ -151,7 +152,10 @@ export async function orchestrateSubagentStream( await forwardEvent(normalizedEvent, options) } - if (normalizedEvent.type === 'structured_result' || normalizedEvent.type === 'subagent_result') { + if ( + normalizedEvent.type === 'structured_result' || + normalizedEvent.type === 'subagent_result' + ) { structuredResult = normalizeStructuredResult(normalizedEvent.data) context.streamComplete = true continue @@ -280,4 +284,3 @@ function buildResult( errors: context.errors.length ? context.errors : undefined, } } - diff --git a/apps/sim/lib/copilot/orchestrator/tool-executor.ts b/apps/sim/lib/copilot/orchestrator/tool-executor.ts index fc223f101..867526395 100644 --- a/apps/sim/lib/copilot/orchestrator/tool-executor.ts +++ b/apps/sim/lib/copilot/orchestrator/tool-executor.ts @@ -12,38 +12,42 @@ import { } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, asc, desc, eq, inArray, isNull, max, or } from 'drizzle-orm' -import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' -import { checkChatAccess, checkWorkflowAccessForChatCreation } from '@/app/api/chat/utils' -import { resolveEnvVarReferences } from '@/executor/utils/reference-validation' -import { normalizeName } from '@/executor/constants' import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/copilot/constants' -import { generateRequestId } from '@/lib/core/utils/request' -import { env } from '@/lib/core/config/env' -import { getEffectiveDecryptedEnv } from '@/lib/environment/utils' -import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace' -import { mcpService } from '@/lib/mcp/service' +import type { + ExecutionContext, + ToolCallResult, + ToolCallState, +} from '@/lib/copilot/orchestrator/types' +import { routeExecution } from '@/lib/copilot/tools/server/router' import { extractWorkflowNames, formatNormalizedWorkflowForCopilot, normalizeWorkflowName, } from '@/lib/copilot/tools/shared/workflow-utils' +import { env } from '@/lib/core/config/env' +import { generateRequestId } from '@/lib/core/utils/request' +import { getEffectiveDecryptedEnv } from '@/lib/environment/utils' +import { mcpService } from '@/lib/mcp/service' +import { sanitizeToolName } from '@/lib/mcp/workflow-tool-schema' +import { listWorkspaceFiles } from '@/lib/uploads/contexts/workspace' +import { getBlockOutputPaths } from '@/lib/workflows/blocks/block-outputs' +import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' +import { executeWorkflow } from '@/lib/workflows/executor/execute-workflow' import { deployWorkflow, loadWorkflowFromNormalizedTables, saveWorkflowToNormalizedTables, undeployWorkflow, } from '@/lib/workflows/persistence/utils' -import { executeWorkflow } from '@/lib/workflows/executor/execute-workflow' -import { BlockPathCalculator } from '@/lib/workflows/blocks/block-path-calculator' -import { getBlockOutputPaths } from '@/lib/workflows/blocks/block-outputs' import { isInputDefinitionTrigger } from '@/lib/workflows/triggers/input-definition-triggers' import { hasValidStartBlock } from '@/lib/workflows/triggers/trigger-utils.server' +import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils' +import { checkChatAccess, checkWorkflowAccessForChatCreation } from '@/app/api/chat/utils' +import { normalizeName } from '@/executor/constants' +import { resolveEnvVarReferences } from '@/executor/utils/reference-validation' import { executeTool } from '@/tools' import { getTool, resolveToolId } from '@/tools/utils' -import { routeExecution } from '@/lib/copilot/tools/server/router' -import { sanitizeToolName } from '@/lib/mcp/workflow-tool-schema' -import type { ExecutionContext, ToolCallResult, ToolCallState } from '@/lib/copilot/orchestrator/types' const logger = createLogger('CopilotToolExecutor') const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT @@ -171,11 +175,9 @@ async function executeIntegrationToolDirect( const decryptedEnvVars = context.decryptedEnvVars || (await getEffectiveDecryptedEnv(userId, workspaceId)) - const executionParams: Record = resolveEnvVarReferences( - toolArgs, - decryptedEnvVars, - { deep: true } - ) as Record + const executionParams: Record = resolveEnvVarReferences(toolArgs, decryptedEnvVars, { + deep: true, + }) as Record if (toolConfig.oauth?.required && toolConfig.oauth.provider) { const provider = toolConfig.oauth.provider @@ -285,7 +287,10 @@ async function executeSimWorkflowTool( } } -async function ensureWorkflowAccess(workflowId: string, userId: string): Promise<{ +async function ensureWorkflowAccess( + workflowId: string, + userId: string +): Promise<{ workflow: typeof workflow.$inferSelect workspaceId?: string | null }> { @@ -538,8 +543,8 @@ async function executeListFolders( context: ExecutionContext ): Promise { try { - const workspaceId = (params?.workspaceId as string | undefined) || - (await getDefaultWorkspaceId(context.userId)) + const workspaceId = + (params?.workspaceId as string | undefined) || (await getDefaultWorkspaceId(context.userId)) await ensureWorkspaceAccess(workspaceId, context.userId, false) @@ -794,9 +799,10 @@ async function executeGetBlockOutputs( const blocks = normalized.blocks || {} const loops = normalized.loops || {} const parallels = normalized.parallels || {} - const blockIds = Array.isArray(params.blockIds) && params.blockIds.length > 0 - ? params.blockIds - : Object.keys(blocks) + const blockIds = + Array.isArray(params.blockIds) && params.blockIds.length > 0 + ? params.blockIds + : Object.keys(blocks) const results: Array<{ blockId: string @@ -935,8 +941,7 @@ async function executeGetBlockUpstreamReferences( for (const accessibleBlockId of accessibleIds) { const block = blocks[accessibleBlockId] if (!block?.type) continue - const canSelfReference = - block.type === 'approval' || block.type === 'human_in_the_loop' + const canSelfReference = block.type === 'approval' || block.type === 'human_in_the_loop' if (accessibleBlockId === blockId && !canSelfReference) continue const blockName = block.name || block.type @@ -1149,7 +1154,12 @@ async function executeDeployApi( return { success: true, - output: { workflowId, isDeployed: true, deployedAt: result.deployedAt, version: result.version }, + output: { + workflowId, + isDeployed: true, + deployedAt: result.deployedAt, + version: result.version, + }, } } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) } @@ -1196,7 +1206,10 @@ async function executeDeployChat( const identifierPattern = /^[a-z0-9-]+$/ if (!identifierPattern.test(identifier)) { - return { success: false, error: 'Identifier can only contain lowercase letters, numbers, and hyphens' } + return { + success: false, + error: 'Identifier can only contain lowercase letters, numbers, and hyphens', + } } const existingIdentifier = await db @@ -1273,7 +1286,10 @@ async function executeDeployChat( }) } - return { success: true, output: { success: true, action: 'deploy', isDeployed: true, identifier } } + return { + success: true, + output: { success: true, action: 'deploy', isDeployed: true, identifier }, + } } catch (error) { return { success: false, error: error instanceof Error ? error.message : String(error) } } @@ -1313,12 +1329,18 @@ async function executeDeployMcp( const existingTool = await db .select() .from(workflowMcpTool) - .where(and(eq(workflowMcpTool.serverId, serverId), eq(workflowMcpTool.workflowId, workflowId))) + .where( + and(eq(workflowMcpTool.serverId, serverId), eq(workflowMcpTool.workflowId, workflowId)) + ) .limit(1) - const toolName = sanitizeToolName(params.toolName || workflowRecord.name || `workflow_${workflowId}`) + const toolName = sanitizeToolName( + params.toolName || workflowRecord.name || `workflow_${workflowId}` + ) const toolDescription = - params.toolDescription || workflowRecord.description || `Execute ${workflowRecord.name} workflow` + params.toolDescription || + workflowRecord.description || + `Execute ${workflowRecord.name} workflow` const parameterSchema = params.parameterSchema || {} if (existingTool.length > 0) { @@ -1387,11 +1409,7 @@ async function executeCheckDeploymentStatus( const workspaceId = workflowRecord.workspaceId const [apiDeploy, chatDeploy] = await Promise.all([ - db - .select() - .from(workflow) - .where(eq(workflow.id, workflowId)) - .limit(1), + db.select().from(workflow).where(eq(workflow.id, workflowId)).limit(1), db.select().from(chat).where(eq(chat.workflowId, workflowId)).limit(1), ]) @@ -1546,10 +1564,7 @@ async function executeCreateWorkspaceMcpServer( const addedTools: Array<{ workflowId: string; toolName: string }> = [] if (workflowIds.length > 0) { - const workflows = await db - .select() - .from(workflow) - .where(inArray(workflow.id, workflowIds)) + const workflows = await db.select().from(workflow).where(inArray(workflow.id, workflowIds)) for (const wf of workflows) { if (wf.workspaceId !== workspaceId || !wf.isDeployed) { @@ -1559,7 +1574,7 @@ async function executeCreateWorkspaceMcpServer( if (!hasStartBlock) { continue } - const toolName = sanitizeToolName(wf.name || `workflow_${wf.id}`) + const toolName = sanitizeToolName(wf.name || `workflow_${wf.id}`) await db.insert(workflowMcpTool).values({ id: crypto.randomUUID(), serverId, @@ -1674,13 +1689,12 @@ export async function prepareExecutionContext( userId: string, workflowId: string ): Promise { - let workspaceId: string | undefined const workflowResult = await db .select({ workspaceId: workflow.workspaceId }) .from(workflow) .where(eq(workflow.id, workflowId)) .limit(1) - workspaceId = workflowResult[0]?.workspaceId ?? undefined + const workspaceId = workflowResult[0]?.workspaceId ?? undefined const decryptedEnvVars = await getEffectiveDecryptedEnv(userId, workspaceId) @@ -1691,4 +1705,3 @@ export async function prepareExecutionContext( decryptedEnvVars, } } - diff --git a/apps/sim/lib/copilot/orchestrator/types.ts b/apps/sim/lib/copilot/orchestrator/types.ts index 52966d0b3..d1f58b588 100644 --- a/apps/sim/lib/copilot/orchestrator/types.ts +++ b/apps/sim/lib/copilot/orchestrator/types.ts @@ -128,4 +128,3 @@ export interface ExecutionContext { workspaceId?: string decryptedEnvVars?: Record } - diff --git a/apps/sim/lib/copilot/tools/mcp/definitions.ts b/apps/sim/lib/copilot/tools/mcp/definitions.ts index 18e076ee9..4ce44089b 100644 --- a/apps/sim/lib/copilot/tools/mcp/definitions.ts +++ b/apps/sim/lib/copilot/tools/mcp/definitions.ts @@ -20,7 +20,8 @@ export const DIRECT_TOOL_DEFS: DirectToolDef[] = [ { name: 'list_workflows', toolId: 'list_user_workflows', - description: 'List all workflows the user has access to. Returns workflow IDs, names, and workspace info.', + description: + 'List all workflows the user has access to. Returns workflow IDs, names, and workspace info.', inputSchema: { type: 'object', properties: { @@ -38,7 +39,8 @@ export const DIRECT_TOOL_DEFS: DirectToolDef[] = [ { name: 'list_workspaces', toolId: 'list_user_workspaces', - description: 'List all workspaces the user has access to. Returns workspace IDs, names, and roles.', + description: + 'List all workspaces the user has access to. Returns workspace IDs, names, and roles.', inputSchema: { type: 'object', properties: {}, @@ -225,10 +227,14 @@ IMPORTANT: Pass the returned plan EXACTLY to copilot_edit - do not modify or sum inputSchema: { type: 'object', properties: { - request: { type: 'string', description: 'What you want to build or modify in the workflow.' }, + request: { + type: 'string', + description: 'What you want to build or modify in the workflow.', + }, workflowId: { type: 'string', - description: 'REQUIRED. The workflow ID. For new workflows, call create_workflow first to get this.', + description: + 'REQUIRED. The workflow ID. For new workflows, call create_workflow first to get this.', }, context: { type: 'object' }, }, @@ -261,15 +267,18 @@ IMPORTANT: After copilot_edit completes, you MUST call copilot_deploy before the message: { type: 'string', description: 'Optional additional instructions for the edit.' }, workflowId: { type: 'string', - description: 'REQUIRED. The workflow ID to edit. Get this from create_workflow for new workflows.', + description: + 'REQUIRED. The workflow ID to edit. Get this from create_workflow for new workflows.', }, plan: { type: 'object', - description: 'The plan object from copilot_plan. Pass it EXACTLY as returned, do not modify.', + description: + 'The plan object from copilot_plan. Pass it EXACTLY as returned, do not modify.', }, context: { type: 'object', - description: 'Additional context. Put the plan in context.plan if not using the plan field directly.', + description: + 'Additional context. Put the plan in context.plan if not using the plan field directly.', }, }, required: ['workflowId'], @@ -463,4 +472,3 @@ USE THIS: }, }, ] - diff --git a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts index a060acfb9..7a22c8075 100644 --- a/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts +++ b/apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts @@ -6,9 +6,9 @@ import { eq } from 'drizzle-orm' import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool' import { validateSelectorIds } from '@/lib/copilot/validation/selector-validator' import type { PermissionGroupConfig } from '@/lib/permission-groups/types' +import { applyAutoLayout } from '@/lib/workflows/autolayout' import { getBlockOutputs } from '@/lib/workflows/blocks/block-outputs' import { extractAndPersistCustomTools } from '@/lib/workflows/persistence/custom-tools-persistence' -import { applyAutoLayout } from '@/lib/workflows/autolayout' import { loadWorkflowFromNormalizedTables, saveWorkflowToNormalizedTables, @@ -3276,9 +3276,8 @@ export const editWorkflowServerTool: BaseServerTool = { padding: { x: 100, y: 100 }, }) - const layoutedBlocks = layoutResult.success && layoutResult.blocks - ? layoutResult.blocks - : finalWorkflowState.blocks + const layoutedBlocks = + layoutResult.success && layoutResult.blocks ? layoutResult.blocks : finalWorkflowState.blocks if (!layoutResult.success) { logger.warn('Autolayout failed, using default positions', { diff --git a/apps/sim/lib/copilot/tools/shared/workflow-utils.ts b/apps/sim/lib/copilot/tools/shared/workflow-utils.ts index 7d8f659ce..2f033a883 100644 --- a/apps/sim/lib/copilot/tools/shared/workflow-utils.ts +++ b/apps/sim/lib/copilot/tools/shared/workflow-utils.ts @@ -26,7 +26,9 @@ export function formatNormalizedWorkflowForCopilot( } export function normalizeWorkflowName(name?: string | null): string { - return String(name || '').trim().toLowerCase() + return String(name || '') + .trim() + .toLowerCase() } export function extractWorkflowNames(workflows: Array<{ name?: string | null }>): string[] { @@ -34,4 +36,3 @@ export function extractWorkflowNames(workflows: Array<{ name?: string | null }>) .map((workflow) => (typeof workflow?.name === 'string' ? workflow.name : null)) .filter((name): name is string => Boolean(name)) } - diff --git a/apps/sim/lib/workflows/utils.ts b/apps/sim/lib/workflows/utils.ts index cfa946749..7f952510f 100644 --- a/apps/sim/lib/workflows/utils.ts +++ b/apps/sim/lib/workflows/utils.ts @@ -48,7 +48,10 @@ export async function resolveWorkflowIdForUser( if (workflowName) { const match = workflows.find( - (w) => String(w.name || '').trim().toLowerCase() === workflowName.toLowerCase() + (w) => + String(w.name || '') + .trim() + .toLowerCase() === workflowName.toLowerCase() ) if (match) { return { workflowId: match.id, workflowName: match.name || undefined } diff --git a/apps/sim/stores/panel/copilot/store.ts b/apps/sim/stores/panel/copilot/store.ts index 95cbfee48..0db3d68c0 100644 --- a/apps/sim/stores/panel/copilot/store.ts +++ b/apps/sim/stores/panel/copilot/store.ts @@ -80,8 +80,8 @@ import { subscriptionKeys } from '@/hooks/queries/subscription' import type { ChatContext, CopilotMessage, - CopilotStreamInfo, CopilotStore, + CopilotStreamInfo, CopilotToolCall, MessageFileAttachment, } from '@/stores/panel/copilot/types' @@ -2251,7 +2251,7 @@ function createOptimizedContentBlocks(contentBlocks: any[]): any[] { } return result } -`` +;`` function updateStreamingMessage(set: any, context: StreamingContext) { if (context.suppressStreamingUpdates) return const now = performance.now() @@ -3124,8 +3124,8 @@ export const useCopilotStore = create()( replayBlocks && replayBlocks.length > 0 ? replayBlocks : bufferedContent - ? [{ type: TEXT_BLOCK_TYPE, content: bufferedContent, timestamp: Date.now() }] - : [], + ? [{ type: TEXT_BLOCK_TYPE, content: bufferedContent, timestamp: Date.now() }] + : [], } nextMessages = [...nextMessages, assistantMessage] } else if (bufferedContent || (replayBlocks && replayBlocks.length > 0)) { @@ -3207,7 +3207,10 @@ export const useCopilotStore = create()( set({ isSendingMessage: false, abortController: null }) } catch (error) { // Handle AbortError gracefully - expected when user aborts - if (error instanceof Error && (error.name === 'AbortError' || error.message.includes('aborted'))) { + if ( + error instanceof Error && + (error.name === 'AbortError' || error.message.includes('aborted')) + ) { logger.info('[Copilot] Resume stream aborted by user') set({ isSendingMessage: false, abortController: null }) return false @@ -4123,7 +4126,6 @@ export const useCopilotStore = create()( logger.info('[AutoAllowedTools] API returned', { toolId, tools: data.autoAllowedTools }) set({ autoAllowedTools: data.autoAllowedTools || [] }) logger.info('[AutoAllowedTools] Added tool to store', { toolId }) - } } catch (err) { logger.error('[AutoAllowedTools] Failed to add tool', { toolId, error: err })