Clean up code

This commit is contained in:
Siddharth Ganesan
2026-02-10 14:59:38 -08:00
parent 5793681189
commit b5b0c39600
6 changed files with 53 additions and 357 deletions

View File

@@ -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<string | null> {
const { message, model, provider } = params
if (!message || !model) return null
const headers: Record<string, string> = {
'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

View File

@@ -624,7 +624,7 @@ async function handleBuildToolCall(
): Promise<CallToolResult> {
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,

View File

@@ -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

View File

@@ -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<string | null> {
if (!message || !model) {
return null
}
const headers: Record<string, string> = {
'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
}
}

View File

@@ -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,
}
}

View File

@@ -10,6 +10,8 @@ interface DocsSearchParams {
threshold?: number
}
const DEFAULT_DOCS_SIMILARITY_THRESHOLD = 0.3
export const searchDocumentationServerTool: BaseServerTool<DocsSearchParams, any> = {
name: 'search_documentation',
async execute(params: DocsSearchParams): Promise<any> {
@@ -19,9 +21,7 @@ export const searchDocumentationServerTool: BaseServerTool<DocsSearchParams, any
logger.info('Executing docs search', { query, topK })
const { getCopilotConfig } = await import('@/lib/copilot/config')
const config = getCopilotConfig()
const similarityThreshold = threshold ?? config.rag.similarityThreshold
const similarityThreshold = threshold ?? DEFAULT_DOCS_SIMILARITY_THRESHOLD
const { generateSearchEmbedding } = await import('@/lib/knowledge/embeddings')
const queryEmbedding = await generateSearchEmbedding(query)