From b5b0c396002a57c1573260fe30091b7a4d89c896 Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Tue, 10 Feb 2026 14:59:38 -0800 Subject: [PATCH] Clean up code --- apps/sim/app/api/copilot/chat/route.ts | 50 ++- apps/sim/app/api/mcp/copilot/route.ts | 4 +- apps/sim/app/api/v1/copilot/chat/route.ts | 2 +- apps/sim/lib/copilot/chat-title.ts | 59 ---- apps/sim/lib/copilot/config.ts | 289 ------------------ .../tools/server/docs/search-documentation.ts | 6 +- 6 files changed, 53 insertions(+), 357 deletions(-) delete mode 100644 apps/sim/lib/copilot/chat-title.ts diff --git a/apps/sim/app/api/copilot/chat/route.ts b/apps/sim/app/api/copilot/chat/route.ts index 8a780ed47..513c0798d 100644 --- a/apps/sim/app/api/copilot/chat/route.ts +++ b/apps/sim/app/api/copilot/chat/route.ts @@ -8,7 +8,7 @@ import { getSession } from '@/lib/auth' import { buildConversationHistory } from '@/lib/copilot/chat-context' import { resolveOrCreateChat } from '@/lib/copilot/chat-lifecycle' import { buildCopilotRequestPayload } from '@/lib/copilot/chat-payload' -import { generateChatTitle } from '@/lib/copilot/chat-title' +import { SIM_AGENT_API_URL } from '@/lib/copilot/constants' import { COPILOT_REQUEST_MODES } from '@/lib/copilot/models' import { orchestrateCopilotStream } from '@/lib/copilot/orchestrator' import { @@ -23,10 +23,54 @@ import { createRequestTracker, createUnauthorizedResponse, } from '@/lib/copilot/request-helpers' +import { env } from '@/lib/core/config/env' import { resolveWorkflowIdForUser } from '@/lib/workflows/utils' const logger = createLogger('CopilotChatAPI') +async function requestChatTitleFromCopilot(params: { + message: string + model: string + provider?: string +}): Promise { + const { message, model, provider } = params + if (!message || !model) return null + + const headers: Record = { + 'Content-Type': 'application/json', + } + if (env.COPILOT_API_KEY) { + headers['x-api-key'] = env.COPILOT_API_KEY + } + + try { + const response = await fetch(`${SIM_AGENT_API_URL}/api/generate-chat-title`, { + method: 'POST', + headers, + body: JSON.stringify({ + message, + model, + ...(provider ? { provider } : {}), + }), + }) + + const payload = await response.json().catch(() => ({})) + if (!response.ok) { + logger.warn('Failed to generate chat title via copilot backend', { + status: response.status, + error: payload, + }) + return null + } + + const title = typeof payload?.title === 'string' ? payload.title.trim() : '' + return title || null + } catch (error) { + logger.error('Error generating chat title:', error) + return null + } +} + const FileAttachmentSchema = z.object({ id: z.string(), key: z.string(), @@ -280,7 +324,7 @@ export async function POST(req: NextRequest) { } if (actualChatId && !currentChat?.title && conversationHistory.length === 0) { - generateChatTitle({ message, model: selectedModel, provider }) + requestChatTitleFromCopilot({ message, model: selectedModel, provider }) .then(async (title) => { if (title) { await db @@ -407,7 +451,7 @@ export async function POST(req: NextRequest) { // Start title generation in parallel if this is first message (non-streaming) if (actualChatId && !currentChat.title && conversationHistory.length === 0) { logger.info(`[${tracker.requestId}] Starting title generation for non-streaming response`) - generateChatTitle({ message, model: selectedModel, provider }) + requestChatTitleFromCopilot({ message, model: selectedModel, provider }) .then(async (title) => { if (title) { await db diff --git a/apps/sim/app/api/mcp/copilot/route.ts b/apps/sim/app/api/mcp/copilot/route.ts index 4d02ab122..79268ecfc 100644 --- a/apps/sim/app/api/mcp/copilot/route.ts +++ b/apps/sim/app/api/mcp/copilot/route.ts @@ -624,7 +624,7 @@ async function handleBuildToolCall( ): Promise { try { const requestText = (args.request as string) || JSON.stringify(args) - const { model } = getCopilotModel('chat') + const { model } = getCopilotModel() const workflowId = args.workflowId as string | undefined const resolved = workflowId ? { workflowId } : await resolveWorkflowIdForUser(userId) @@ -721,7 +721,7 @@ async function handleSubagentToolCall( context.plan = args.plan } - const { model } = getCopilotModel('chat') + const { model } = getCopilotModel() const result = await orchestrateSubagentStream( toolDef.agentId, diff --git a/apps/sim/app/api/v1/copilot/chat/route.ts b/apps/sim/app/api/v1/copilot/chat/route.ts index d08234cff..fbce426bb 100644 --- a/apps/sim/app/api/v1/copilot/chat/route.ts +++ b/apps/sim/app/api/v1/copilot/chat/route.ts @@ -42,7 +42,7 @@ export async function POST(req: NextRequest) { try { const body = await req.json() const parsed = RequestSchema.parse(body) - const defaults = getCopilotModel('chat') + const defaults = getCopilotModel() const selectedModel = parsed.model || defaults.model // Resolve workflow ID diff --git a/apps/sim/lib/copilot/chat-title.ts b/apps/sim/lib/copilot/chat-title.ts deleted file mode 100644 index 6f890338a..000000000 --- a/apps/sim/lib/copilot/chat-title.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { createLogger } from '@sim/logger' -import { SIM_AGENT_API_URL } from '@/lib/copilot/constants' -import { env } from '@/lib/core/config/env' - -const logger = createLogger('SimAgentUtils') - -interface GenerateChatTitleParams { - message: string - model: string - provider?: string -} - -/** - * Generates a short title for a chat based on the first message - * using the Copilot backend's server-side provider configuration. - */ -export async function generateChatTitle({ - message, - model, - provider, -}: GenerateChatTitleParams): Promise { - if (!message || !model) { - return null - } - - const headers: Record = { - 'Content-Type': 'application/json', - } - if (env.COPILOT_API_KEY) { - headers['x-api-key'] = env.COPILOT_API_KEY - } - - try { - const response = await fetch(`${SIM_AGENT_API_URL}/api/generate-chat-title`, { - method: 'POST', - headers, - body: JSON.stringify({ - message, - model, - ...(provider ? { provider } : {}), - }), - }) - - const payload = await response.json().catch(() => ({})) - if (!response.ok) { - logger.warn('Failed to generate chat title via copilot backend', { - status: response.status, - error: payload, - }) - return null - } - - const title = typeof payload?.title === 'string' ? payload.title.trim() : '' - return title || null - } catch (error) { - logger.error('Error generating chat title:', error) - return null - } -} diff --git a/apps/sim/lib/copilot/config.ts b/apps/sim/lib/copilot/config.ts index ad2bfb483..e69de29bb 100644 --- a/apps/sim/lib/copilot/config.ts +++ b/apps/sim/lib/copilot/config.ts @@ -1,289 +0,0 @@ -import { createLogger } from '@sim/logger' -import { AGENT_MODE_SYSTEM_PROMPT } from '@/lib/copilot/prompts' -import { getProviderDefaultModel } from '@/providers/models' -import type { ProviderId } from '@/providers/types' - -const logger = createLogger('CopilotConfig') - -/** - * Configuration validation constraints - */ -const VALIDATION_CONSTRAINTS = { - temperature: { min: 0, max: 2 }, - maxTokens: { min: 1, max: 100000 }, - maxSources: { min: 1, max: 20 }, - similarityThreshold: { min: 0, max: 1 }, - maxConversationHistory: { min: 1, max: 50 }, -} as const - -/** - * Copilot model types - */ -export type CopilotModelType = 'chat' | 'rag' | 'title' - -/** - * Configuration validation result - */ -export interface ValidationResult { - isValid: boolean - errors: string[] -} - -/** - * Copilot configuration interface - */ -export interface CopilotConfig { - // Chat LLM configuration - chat: { - defaultProvider: ProviderId - defaultModel: string - temperature: number - maxTokens: number - systemPrompt: string - } - // RAG (documentation search) LLM configuration - rag: { - defaultProvider: ProviderId - defaultModel: string - temperature: number - maxTokens: number - embeddingModel: string - maxSources: number - similarityThreshold: number - } - // General configuration - general: { - streamingEnabled: boolean - maxConversationHistory: number - titleGenerationModel: string - } -} - -function parseFloatEnv(value: string | undefined, name: string): number | null { - if (!value) return null - const parsed = Number.parseFloat(value) - if (Number.isNaN(parsed)) { - logger.warn(`Invalid ${name}: ${value}. Expected a valid number.`) - return null - } - return parsed -} - -function parseIntEnv(value: string | undefined, name: string): number | null { - if (!value) return null - const parsed = Number.parseInt(value, 10) - if (Number.isNaN(parsed)) { - logger.warn(`Invalid ${name}: ${value}. Expected a valid integer.`) - return null - } - return parsed -} - -function parseBooleanEnv(value: string | undefined): boolean | null { - if (!value) return null - return value.toLowerCase() === 'true' -} - -export const DEFAULT_COPILOT_CONFIG: CopilotConfig = { - chat: { - defaultProvider: 'anthropic', - defaultModel: 'claude-4.6-opus', - temperature: 0.1, - maxTokens: 8192, - systemPrompt: AGENT_MODE_SYSTEM_PROMPT, - }, - rag: { - defaultProvider: 'anthropic', - defaultModel: 'claude-4.6-opus', - temperature: 0.1, - maxTokens: 2000, - embeddingModel: 'text-embedding-3-small', - maxSources: 10, - similarityThreshold: 0.3, - }, - general: { - streamingEnabled: true, - maxConversationHistory: 10, - titleGenerationModel: 'claude-3-haiku-20240307', - }, -} - -function applyEnvironmentOverrides(config: CopilotConfig): void { - const chatTemperature = parseFloatEnv( - process.env.COPILOT_CHAT_TEMPERATURE, - 'COPILOT_CHAT_TEMPERATURE' - ) - if (chatTemperature !== null) { - config.chat.temperature = chatTemperature - } - - const chatMaxTokens = parseIntEnv(process.env.COPILOT_CHAT_MAX_TOKENS, 'COPILOT_CHAT_MAX_TOKENS') - if (chatMaxTokens !== null) { - config.chat.maxTokens = chatMaxTokens - } - - const ragTemperature = parseFloatEnv( - process.env.COPILOT_RAG_TEMPERATURE, - 'COPILOT_RAG_TEMPERATURE' - ) - if (ragTemperature !== null) { - config.rag.temperature = ragTemperature - } - - const ragMaxTokens = parseIntEnv(process.env.COPILOT_RAG_MAX_TOKENS, 'COPILOT_RAG_MAX_TOKENS') - if (ragMaxTokens !== null) { - config.rag.maxTokens = ragMaxTokens - } - - const ragMaxSources = parseIntEnv(process.env.COPILOT_RAG_MAX_SOURCES, 'COPILOT_RAG_MAX_SOURCES') - if (ragMaxSources !== null) { - config.rag.maxSources = ragMaxSources - } - - const ragSimilarityThreshold = parseFloatEnv( - process.env.COPILOT_RAG_SIMILARITY_THRESHOLD, - 'COPILOT_RAG_SIMILARITY_THRESHOLD' - ) - if (ragSimilarityThreshold !== null) { - config.rag.similarityThreshold = ragSimilarityThreshold - } - - const streamingEnabled = parseBooleanEnv(process.env.COPILOT_STREAMING_ENABLED) - if (streamingEnabled !== null) { - config.general.streamingEnabled = streamingEnabled - } - - const maxConversationHistory = parseIntEnv( - process.env.COPILOT_MAX_CONVERSATION_HISTORY, - 'COPILOT_MAX_CONVERSATION_HISTORY' - ) - if (maxConversationHistory !== null) { - config.general.maxConversationHistory = maxConversationHistory - } - - if (process.env.COPILOT_TITLE_GENERATION_MODEL) { - config.general.titleGenerationModel = process.env.COPILOT_TITLE_GENERATION_MODEL - } -} - -export function getCopilotConfig(): CopilotConfig { - const config = structuredClone(DEFAULT_COPILOT_CONFIG) - - try { - applyEnvironmentOverrides(config) - } catch (error) { - logger.warn('Error applying environment variable overrides, using defaults', { error }) - } - - return config -} - -export function getCopilotModel(type: CopilotModelType): { - provider: ProviderId - model: string -} { - const config = getCopilotConfig() - - switch (type) { - case 'chat': - return { - provider: config.chat.defaultProvider, - model: config.chat.defaultModel, - } - case 'rag': - return { - provider: config.rag.defaultProvider, - model: config.rag.defaultModel, - } - case 'title': - return { - provider: config.chat.defaultProvider, - model: config.general.titleGenerationModel, - } - default: - throw new Error(`Unknown copilot model type: ${type}`) - } -} - -function validateNumericValue( - value: number, - constraint: { min: number; max: number }, - name: string -): string | null { - if (value < constraint.min || value > constraint.max) { - return `${name} must be between ${constraint.min} and ${constraint.max}` - } - return null -} - -export function validateCopilotConfig(config: CopilotConfig): ValidationResult { - const errors: string[] = [] - - try { - const chatDefaultModel = getProviderDefaultModel(config.chat.defaultProvider) - if (!chatDefaultModel) { - errors.push(`Chat provider '${config.chat.defaultProvider}' not found`) - } - } catch (error) { - errors.push(`Invalid chat provider: ${config.chat.defaultProvider}`) - } - - try { - const ragDefaultModel = getProviderDefaultModel(config.rag.defaultProvider) - if (!ragDefaultModel) { - errors.push(`RAG provider '${config.rag.defaultProvider}' not found`) - } - } catch (error) { - errors.push(`Invalid RAG provider: ${config.rag.defaultProvider}`) - } - - const validationChecks = [ - { - value: config.chat.temperature, - constraint: VALIDATION_CONSTRAINTS.temperature, - name: 'Chat temperature', - }, - { - value: config.rag.temperature, - constraint: VALIDATION_CONSTRAINTS.temperature, - name: 'RAG temperature', - }, - { - value: config.chat.maxTokens, - constraint: VALIDATION_CONSTRAINTS.maxTokens, - name: 'Chat maxTokens', - }, - { - value: config.rag.maxTokens, - constraint: VALIDATION_CONSTRAINTS.maxTokens, - name: 'RAG maxTokens', - }, - { - value: config.rag.maxSources, - constraint: VALIDATION_CONSTRAINTS.maxSources, - name: 'RAG maxSources', - }, - { - value: config.rag.similarityThreshold, - constraint: VALIDATION_CONSTRAINTS.similarityThreshold, - name: 'RAG similarityThreshold', - }, - { - value: config.general.maxConversationHistory, - constraint: VALIDATION_CONSTRAINTS.maxConversationHistory, - name: 'General maxConversationHistory', - }, - ] - - for (const check of validationChecks) { - const error = validateNumericValue(check.value, check.constraint, check.name) - if (error) { - errors.push(error) - } - } - - return { - isValid: errors.length === 0, - errors, - } -} diff --git a/apps/sim/lib/copilot/tools/server/docs/search-documentation.ts b/apps/sim/lib/copilot/tools/server/docs/search-documentation.ts index 0fe3eb413..a8ac01539 100644 --- a/apps/sim/lib/copilot/tools/server/docs/search-documentation.ts +++ b/apps/sim/lib/copilot/tools/server/docs/search-documentation.ts @@ -10,6 +10,8 @@ interface DocsSearchParams { threshold?: number } +const DEFAULT_DOCS_SIMILARITY_THRESHOLD = 0.3 + export const searchDocumentationServerTool: BaseServerTool = { name: 'search_documentation', async execute(params: DocsSearchParams): Promise { @@ -19,9 +21,7 @@ export const searchDocumentationServerTool: BaseServerTool