From 8dbec38a248463944dcc461481c33068c00962a4 Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Tue, 10 Feb 2026 13:13:42 -0800 Subject: [PATCH] Consolidation --- apps/sim/app/api/copilot/chat/route.ts | 19 ++--- apps/sim/app/api/copilot/models/route.ts | 22 +++++- apps/sim/app/api/copilot/user-models/route.ts | 26 ++----- apps/sim/lib/copilot/chat-title.ts | 78 +++++++------------ apps/sim/lib/copilot/config.ts | 48 ------------ apps/sim/lib/copilot/models.ts | 26 ------- apps/sim/lib/copilot/orchestrator/types.ts | 4 +- apps/sim/lib/copilot/types.ts | 31 -------- apps/sim/lib/core/config/env.ts | 2 - apps/sim/stores/panel/copilot/store.ts | 6 +- 10 files changed, 63 insertions(+), 199 deletions(-) diff --git a/apps/sim/app/api/copilot/chat/route.ts b/apps/sim/app/api/copilot/chat/route.ts index 51568ed5f..8a780ed47 100644 --- a/apps/sim/app/api/copilot/chat/route.ts +++ b/apps/sim/app/api/copilot/chat/route.ts @@ -9,7 +9,6 @@ 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 { getCopilotModel } from '@/lib/copilot/config' import { COPILOT_REQUEST_MODES } from '@/lib/copilot/models' import { orchestrateCopilotStream } from '@/lib/copilot/orchestrator' import { @@ -24,7 +23,6 @@ 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') @@ -43,7 +41,7 @@ const ChatMessageSchema = z.object({ chatId: z.string().optional(), workflowId: z.string().optional(), workflowName: z.string().optional(), - model: z.string().optional().default('claude-4.6-opus'), + model: z.string().optional().default('claude-opus-4-6'), mode: z.enum(COPILOT_REQUEST_MODES).optional().default('agent'), prefetch: z.boolean().optional(), createNewChat: z.boolean().optional().default(false), @@ -173,14 +171,14 @@ export async function POST(req: NextRequest) { let currentChat: any = null let conversationHistory: any[] = [] let actualChatId = chatId + const selectedModel = model || 'claude-opus-4-6' if (chatId || createNewChat) { - const defaultsForChatRow = getCopilotModel('chat') const chatResult = await resolveOrCreateChat({ chatId, userId: authenticatedUserId, workflowId, - model: defaultsForChatRow.model, + model: selectedModel, }) currentChat = chatResult.chat actualChatId = chatResult.chatId || chatId @@ -191,8 +189,6 @@ export async function POST(req: NextRequest) { conversationHistory = history.history } - const defaults = getCopilotModel('chat') - const selectedModel = model || defaults.model const effectiveMode = mode === 'agent' ? 'build' : mode const effectiveConversationId = (currentChat?.conversationId as string | undefined) || conversationId @@ -284,7 +280,7 @@ export async function POST(req: NextRequest) { } if (actualChatId && !currentChat?.title && conversationHistory.length === 0) { - generateChatTitle(message) + generateChatTitle({ message, model: selectedModel, provider }) .then(async (title) => { if (title) { await db @@ -373,10 +369,7 @@ export async function POST(req: NextRequest) { content: nonStreamingResult.content, toolCalls: nonStreamingResult.toolCalls, model: selectedModel, - provider: - (requestPayload?.provider as Record)?.provider || - env.COPILOT_PROVIDER || - 'openai', + provider: typeof requestPayload?.provider === 'string' ? requestPayload.provider : undefined, } logger.info(`[${tracker.requestId}] Non-streaming response from orchestrator:`, { @@ -414,7 +407,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) + generateChatTitle({ message, model: selectedModel, provider }) .then(async (title) => { if (title) { await db diff --git a/apps/sim/app/api/copilot/models/route.ts b/apps/sim/app/api/copilot/models/route.ts index dfd471ff5..d17737974 100644 --- a/apps/sim/app/api/copilot/models/route.ts +++ b/apps/sim/app/api/copilot/models/route.ts @@ -2,11 +2,27 @@ import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { SIM_AGENT_API_URL } from '@/lib/copilot/constants' import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request-helpers' -import { env } from '@/lib/core/config/env' import type { AvailableModel } from '@/lib/copilot/types' +import { env } from '@/lib/core/config/env' const logger = createLogger('CopilotModelsAPI') +interface RawAvailableModel { + id: string + friendlyName?: string + displayName?: string + provider?: string +} + +function isRawAvailableModel(item: unknown): item is RawAvailableModel { + return ( + typeof item === 'object' && + item !== null && + 'id' in item && + typeof (item as { id: unknown }).id === 'string' + ) +} + export async function GET(_req: NextRequest) { const { userId, isAuthenticated } = await authenticateCopilotRequestSessionOnly() if (!isAuthenticated || !userId) { @@ -44,8 +60,8 @@ export async function GET(_req: NextRequest) { const rawModels = Array.isArray(payload?.models) ? payload.models : [] const models: AvailableModel[] = rawModels - .filter((item: any) => item && typeof item.id === 'string') - .map((item: any) => ({ + .filter((item: unknown): item is RawAvailableModel => isRawAvailableModel(item)) + .map((item: RawAvailableModel) => ({ id: item.id, friendlyName: item.friendlyName || item.displayName || item.id, provider: item.provider || 'unknown', diff --git a/apps/sim/app/api/copilot/user-models/route.ts b/apps/sim/app/api/copilot/user-models/route.ts index 86e31c747..6ead89a41 100644 --- a/apps/sim/app/api/copilot/user-models/route.ts +++ b/apps/sim/app/api/copilot/user-models/route.ts @@ -9,28 +9,12 @@ import { settings } from '@/../../packages/db/schema' const logger = createLogger('CopilotUserModelsAPI') const DEFAULT_ENABLED_MODELS: Record = { - 'gpt-4o': false, - 'gpt-4.1': false, - 'gpt-5-fast': false, - 'gpt-5': true, - 'gpt-5-medium': false, - 'gpt-5-high': false, - 'gpt-5.1-fast': false, - 'gpt-5.1': false, - 'gpt-5.1-medium': false, - 'gpt-5.1-high': false, - 'gpt-5-codex': false, - 'gpt-5.1-codex': false, - 'gpt-5.2': false, + 'claude-opus-4-6': true, + 'claude-opus-4-5': true, + 'claude-sonnet-4-5': true, + 'claude-haiku-4-5': true, + 'gpt-5.2': true, 'gpt-5.2-codex': true, - 'gpt-5.2-pro': true, - o3: true, - 'claude-4-sonnet': false, - 'claude-4.5-haiku': true, - 'claude-4.5-sonnet': true, - 'claude-4.6-opus': true, - 'claude-4.5-opus': true, - 'claude-4.1-opus': false, 'gemini-3-pro': true, } diff --git a/apps/sim/lib/copilot/chat-title.ts b/apps/sim/lib/copilot/chat-title.ts index 10dd88299..6f890338a 100644 --- a/apps/sim/lib/copilot/chat-title.ts +++ b/apps/sim/lib/copilot/chat-title.ts @@ -1,77 +1,57 @@ import { createLogger } from '@sim/logger' +import { SIM_AGENT_API_URL } from '@/lib/copilot/constants' import { env } from '@/lib/core/config/env' -import { extractResponseText } from '@/providers/openai/utils' const logger = createLogger('SimAgentUtils') -const azureApiKey = env.AZURE_OPENAI_API_KEY -const azureEndpoint = env.AZURE_OPENAI_ENDPOINT -const azureApiVersion = env.AZURE_OPENAI_API_VERSION -const chatTitleModelName = env.WAND_OPENAI_MODEL_NAME || 'gpt-4o' -const openaiApiKey = env.OPENAI_API_KEY - -const useChatTitleAzure = azureApiKey && azureEndpoint && azureApiVersion +interface GenerateChatTitleParams { + message: string + model: string + provider?: string +} /** * Generates a short title for a chat based on the first message - * @param message First user message in the chat - * @returns A short title or null if API key is not available + * using the Copilot backend's server-side provider configuration. */ -export async function generateChatTitle(message: string): Promise { - if (!useChatTitleAzure && !openaiApiKey) { +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 apiUrl = useChatTitleAzure - ? `${azureEndpoint?.replace(/\/$/, '')}/openai/v1/responses?api-version=${azureApiVersion}` - : 'https://api.openai.com/v1/responses' - - const headers: Record = { - 'Content-Type': 'application/json', - 'OpenAI-Beta': 'responses=v1', - } - - if (useChatTitleAzure) { - headers['api-key'] = azureApiKey! - } else { - headers.Authorization = `Bearer ${openaiApiKey}` - } - - const response = await fetch(apiUrl, { + const response = await fetch(`${SIM_AGENT_API_URL}/api/generate-chat-title`, { method: 'POST', headers, body: JSON.stringify({ - model: useChatTitleAzure ? chatTitleModelName : 'gpt-4o', - input: [ - { - role: 'system', - content: - 'Generate a very short title (3-5 words max) for a chat that starts with this message. The title should be concise and descriptive. Do not wrap the title in quotes.', - }, - { - role: 'user', - content: message, - }, - ], - max_output_tokens: 20, - temperature: 0.2, + message, + model, + ...(provider ? { provider } : {}), }), }) + const payload = await response.json().catch(() => ({})) if (!response.ok) { - const errorText = await response.text() - logger.error('Error generating chat title:', { + logger.warn('Failed to generate chat title via copilot backend', { status: response.status, - statusText: response.statusText, - error: errorText, + error: payload, }) return null } - const data = await response.json() - const title = extractResponseText(data.output)?.trim() || null - return title + 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 d82a63012..ad2bfb483 100644 --- a/apps/sim/lib/copilot/config.ts +++ b/apps/sim/lib/copilot/config.ts @@ -5,23 +5,6 @@ import type { ProviderId } from '@/providers/types' const logger = createLogger('CopilotConfig') -/** - * Valid provider IDs for validation - */ -const VALID_PROVIDER_IDS: readonly ProviderId[] = [ - 'openai', - 'azure-openai', - 'anthropic', - 'azure-anthropic', - 'google', - 'deepseek', - 'xai', - 'cerebras', - 'mistral', - 'groq', - 'ollama', -] as const - /** * Configuration validation constraints */ @@ -76,11 +59,6 @@ export interface CopilotConfig { } } -function validateProviderId(value: string | undefined): ProviderId | null { - if (!value) return null - return VALID_PROVIDER_IDS.includes(value as ProviderId) ? (value as ProviderId) : null -} - function parseFloatEnv(value: string | undefined, name: string): number | null { if (!value) return null const parsed = Number.parseFloat(value) @@ -131,19 +109,6 @@ export const DEFAULT_COPILOT_CONFIG: CopilotConfig = { } function applyEnvironmentOverrides(config: CopilotConfig): void { - const chatProvider = validateProviderId(process.env.COPILOT_CHAT_PROVIDER) - if (chatProvider) { - config.chat.defaultProvider = chatProvider - } else if (process.env.COPILOT_CHAT_PROVIDER) { - logger.warn( - `Invalid COPILOT_CHAT_PROVIDER: ${process.env.COPILOT_CHAT_PROVIDER}. Valid providers: ${VALID_PROVIDER_IDS.join(', ')}` - ) - } - - if (process.env.COPILOT_CHAT_MODEL) { - config.chat.defaultModel = process.env.COPILOT_CHAT_MODEL - } - const chatTemperature = parseFloatEnv( process.env.COPILOT_CHAT_TEMPERATURE, 'COPILOT_CHAT_TEMPERATURE' @@ -157,19 +122,6 @@ function applyEnvironmentOverrides(config: CopilotConfig): void { config.chat.maxTokens = chatMaxTokens } - const ragProvider = validateProviderId(process.env.COPILOT_RAG_PROVIDER) - if (ragProvider) { - config.rag.defaultProvider = ragProvider - } else if (process.env.COPILOT_RAG_PROVIDER) { - logger.warn( - `Invalid COPILOT_RAG_PROVIDER: ${process.env.COPILOT_RAG_PROVIDER}. Valid providers: ${VALID_PROVIDER_IDS.join(', ')}` - ) - } - - if (process.env.COPILOT_RAG_MODEL) { - config.rag.defaultModel = process.env.COPILOT_RAG_MODEL - } - const ragTemperature = parseFloatEnv( process.env.COPILOT_RAG_TEMPERATURE, 'COPILOT_RAG_TEMPERATURE' diff --git a/apps/sim/lib/copilot/models.ts b/apps/sim/lib/copilot/models.ts index 96b03e660..f102de517 100644 --- a/apps/sim/lib/copilot/models.ts +++ b/apps/sim/lib/copilot/models.ts @@ -1,29 +1,3 @@ -export const COPILOT_MODEL_IDS = [ - 'gpt-5-fast', - 'gpt-5', - 'gpt-5-medium', - 'gpt-5-high', - 'gpt-5.1-fast', - 'gpt-5.1', - 'gpt-5.1-medium', - 'gpt-5.1-high', - 'gpt-5-codex', - 'gpt-5.1-codex', - 'gpt-5.2', - 'gpt-5.2-codex', - 'gpt-5.2-pro', - 'gpt-4o', - 'gpt-4.1', - 'o3', - 'claude-4-sonnet', - 'claude-4.5-haiku', - 'claude-4.5-sonnet', - 'claude-4.6-opus', - 'claude-4.5-opus', - 'claude-4.1-opus', - 'gemini-3-pro', -] as const - export type CopilotModelId = string export const COPILOT_MODES = ['ask', 'build', 'plan'] as const diff --git a/apps/sim/lib/copilot/orchestrator/types.ts b/apps/sim/lib/copilot/orchestrator/types.ts index eebc806a7..3113a23b5 100644 --- a/apps/sim/lib/copilot/orchestrator/types.ts +++ b/apps/sim/lib/copilot/orchestrator/types.ts @@ -1,5 +1,3 @@ -import type { CopilotProviderConfig } from '@/lib/copilot/types' - export type SSEEventType = | 'chat_id' | 'title_updated' @@ -104,7 +102,7 @@ export interface OrchestratorRequest { contexts?: Array<{ type: string; content: string }> fileAttachments?: FileAttachment[] commands?: string[] - provider?: CopilotProviderConfig + provider?: string streamToolCalls?: boolean version?: string prefetch?: boolean diff --git a/apps/sim/lib/copilot/types.ts b/apps/sim/lib/copilot/types.ts index 302f55064..79c617f01 100644 --- a/apps/sim/lib/copilot/types.ts +++ b/apps/sim/lib/copilot/types.ts @@ -1,4 +1,3 @@ -import type { ProviderId } from '@/providers/types' import type { CopilotToolCall, ToolState } from '@/stores/panel' export type NotificationStatus = @@ -16,33 +15,3 @@ export interface AvailableModel { friendlyName: string provider: string } - -// Provider configuration for Sim Agent requests. -// This type is only for the `provider` field in requests sent to the Sim Agent. -export type CopilotProviderConfig = - | { - provider: 'azure-openai' - model: string - apiKey?: string - apiVersion?: string - endpoint?: string - } - | { - provider: 'azure-anthropic' - model: string - apiKey?: string - apiVersion?: string - endpoint?: string - } - | { - provider: 'vertex' - model: string - apiKey?: string - vertexProject?: string - vertexLocation?: string - } - | { - provider: Exclude - model?: string - apiKey?: string - } diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 0299ade0e..31c9c36ad 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -29,8 +29,6 @@ export const env = createEnv({ INTERNAL_API_SECRET: z.string().min(32), // Secret for internal API authentication // Copilot - COPILOT_PROVIDER: z.string().optional(), // Provider for copilot API calls - COPILOT_MODEL: z.string().optional(), // Model for copilot API calls COPILOT_API_KEY: z.string().min(1).optional(), // Secret for internal sim agent API authentication SIM_AGENT_API_URL: z.string().url().optional(), // URL for internal sim agent API AGENT_INDEXER_URL: z.string().url().optional(), // URL for agent training data indexer diff --git a/apps/sim/stores/panel/copilot/store.ts b/apps/sim/stores/panel/copilot/store.ts index 379fb7002..3eddf5429 100644 --- a/apps/sim/stores/panel/copilot/store.ts +++ b/apps/sim/stores/panel/copilot/store.ts @@ -42,7 +42,6 @@ import { saveMessageCheckpoint, } from '@/lib/copilot/messages' import type { CopilotTransportMode } from '@/lib/copilot/models' -import type { AvailableModel } from '@/lib/copilot/types' import { parseSSEStream } from '@/lib/copilot/orchestrator/sse-parser' import { abortAllInProgressTools, @@ -52,6 +51,7 @@ import { stripTodoTags, } from '@/lib/copilot/store-utils' import { ClientToolCallState } from '@/lib/copilot/tools/client/tool-display-registry' +import type { AvailableModel } from '@/lib/copilot/types' import { getQueryClient } from '@/app/_shell/providers/query-provider' import { subscriptionKeys } from '@/hooks/queries/subscription' import type { @@ -577,7 +577,7 @@ async function finalizeStream( errorType = 'usage_limit' } else if (result.status === 403) { errorContent = - '_Provider config not allowed for non-enterprise users. Please remove the provider config and try again_' + '_Access denied by the Copilot backend. Please verify your API key and server configuration._' errorType = 'forbidden' } else if (result.status === 426) { errorContent = @@ -2246,7 +2246,7 @@ export const useCopilotStore = create()( const provider = model.provider || 'unknown' // Use composite provider/modelId keys (matching agent block pattern in providers/models.ts) // so models with the same raw ID from different providers are uniquely identified. - const compositeId = provider ? `${provider}/${model.id}` : model.id + const compositeId = `${provider}/${model.id}` return { id: compositeId, friendlyName: model.friendlyName || model.id,