mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-04 19:55:08 -05:00
Fix lint
This commit is contained in:
@@ -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(),
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -34,4 +34,3 @@ export const SUBAGENT_TOOL_NAMES = [
|
||||
] as const
|
||||
|
||||
export const SUBAGENT_TOOL_SET = new Set<string>(SUBAGENT_TOOL_NAMES)
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
export async function updateChatConversationId(
|
||||
chatId: string,
|
||||
conversationId: string
|
||||
): Promise<void> {
|
||||
await db
|
||||
.update(copilotChats)
|
||||
.set({
|
||||
@@ -135,4 +138,3 @@ export async function getToolConfirmation(toolCallId: string): Promise<{
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string>()
|
||||
|
||||
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<void>
|
||||
|
||||
function addContentBlock(
|
||||
context: StreamingContext,
|
||||
block: Omit<ContentBlock, 'timestamp'>
|
||||
): void {
|
||||
function addContentBlock(context: StreamingContext, block: Omit<ContentBlock, 'timestamp'>): 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<string, SSEHandler> = {
|
||||
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<string, SSEHandler> = {
|
||||
|
||||
// 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<string, SSEHandler> = {
|
||||
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<string, SSEHandler> = {
|
||||
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<string, SSEHandler> = {
|
||||
},
|
||||
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<string, SSEHandler> = {
|
||||
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<string, SSEHandler> = {
|
||||
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<string, SSEHandler> = {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -69,4 +69,3 @@ export async function* parseSSEStream(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,7 @@ export async function resetStreamBuffer(streamId: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function setStreamMeta(
|
||||
streamId: string,
|
||||
meta: StreamMeta
|
||||
): Promise<void> {
|
||||
export async function setStreamMeta(streamId: string, meta: StreamMeta): Promise<void> {
|
||||
const redis = getRedisClient()
|
||||
if (!redis) return
|
||||
try {
|
||||
@@ -263,4 +260,3 @@ export async function readStreamEvents(
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, any> = resolveEnvVarReferences(
|
||||
toolArgs,
|
||||
decryptedEnvVars,
|
||||
{ deep: true }
|
||||
) as Record<string, any>
|
||||
const executionParams: Record<string, any> = resolveEnvVarReferences(toolArgs, decryptedEnvVars, {
|
||||
deep: true,
|
||||
}) as Record<string, any>
|
||||
|
||||
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<ToolCallResult> {
|
||||
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<ExecutionContext> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,4 +128,3 @@ export interface ExecutionContext {
|
||||
workspaceId?: string
|
||||
decryptedEnvVars?: Record<string, string>
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -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<EditWorkflowParams, any> = {
|
||||
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', {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<CopilotStore>()(
|
||||
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<CopilotStore>()(
|
||||
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<CopilotStore>()(
|
||||
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 })
|
||||
|
||||
Reference in New Issue
Block a user