From 2a7ebfb3961ba98821302942c3f7eb16bf3c8a69 Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Fri, 6 Feb 2026 17:31:52 -0800 Subject: [PATCH] Add opus 4.6 --- apps/docs/content/docs/en/copilot/index.mdx | 2 +- apps/sim/app/api/copilot/chat/route.ts | 2 +- apps/sim/app/api/copilot/user-models/route.ts | 1 + apps/sim/app/api/mcp/copilot/route.ts | 136 ++++++++++++++---- .../components/user-input/constants.ts | 1 + apps/sim/lib/copilot/config.ts | 4 +- apps/sim/lib/copilot/models.ts | 1 + apps/sim/lib/core/config/feature-flags.ts | 6 +- apps/sim/stores/panel/copilot/store.ts | 2 +- 9 files changed, 119 insertions(+), 36 deletions(-) diff --git a/apps/docs/content/docs/en/copilot/index.mdx b/apps/docs/content/docs/en/copilot/index.mdx index e222d8e55..3bdb0a579 100644 --- a/apps/docs/content/docs/en/copilot/index.mdx +++ b/apps/docs/content/docs/en/copilot/index.mdx @@ -56,7 +56,7 @@ Switch between modes using the mode selector at the bottom of the input area. Select your preferred AI model using the model selector at the bottom right of the input area. **Available Models:** -- Claude 4.5 Opus, Sonnet (default), Haiku +- Claude 4.6 Opus (default), 4.5 Opus, Sonnet, Haiku - GPT 5.2 Codex, Pro - Gemini 3 Pro diff --git a/apps/sim/app/api/copilot/chat/route.ts b/apps/sim/app/api/copilot/chat/route.ts index dbd97eccf..248298348 100644 --- a/apps/sim/app/api/copilot/chat/route.ts +++ b/apps/sim/app/api/copilot/chat/route.ts @@ -43,7 +43,7 @@ const ChatMessageSchema = z.object({ chatId: z.string().optional(), workflowId: z.string().optional(), workflowName: z.string().optional(), - model: z.enum(COPILOT_MODEL_IDS).optional().default('claude-4.5-opus'), + model: z.enum(COPILOT_MODEL_IDS).optional().default('claude-4.6-opus'), mode: z.enum(COPILOT_REQUEST_MODES).optional().default('agent'), prefetch: z.boolean().optional(), createNewChat: z.boolean().optional().default(false), diff --git a/apps/sim/app/api/copilot/user-models/route.ts b/apps/sim/app/api/copilot/user-models/route.ts index ead14a5e9..86e31c747 100644 --- a/apps/sim/app/api/copilot/user-models/route.ts +++ b/apps/sim/app/api/copilot/user-models/route.ts @@ -28,6 +28,7 @@ const DEFAULT_ENABLED_MODELS: Record = { '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/app/api/mcp/copilot/route.ts b/apps/sim/app/api/mcp/copilot/route.ts index 67f985942..7692ef532 100644 --- a/apps/sim/app/api/mcp/copilot/route.ts +++ b/apps/sim/app/api/mcp/copilot/route.ts @@ -16,10 +16,11 @@ import { createLogger } from '@sim/logger' import { randomUUID } from 'node:crypto' import { eq, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { authenticateApiKeyFromHeader, updateApiKeyLastUsed } from '@/lib/api-key/service' -import { checkServerSideUsageLimits } from '@/lib/billing/calculations/usage-monitor' +import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { getCopilotModel } from '@/lib/copilot/config' -import { SIM_AGENT_VERSION } from '@/lib/copilot/constants' +import { SIM_AGENT_API_URL, SIM_AGENT_VERSION } from '@/lib/copilot/constants' +import { RateLimiter } from '@/lib/core/rate-limiter' +import { env } from '@/lib/core/config/env' import { orchestrateCopilotStream } from '@/lib/copilot/orchestrator' import { orchestrateSubagentStream } from '@/lib/copilot/orchestrator/subagent' import { @@ -30,10 +31,78 @@ import { DIRECT_TOOL_DEFS, SUBAGENT_TOOL_DEFS } from '@/lib/copilot/tools/mcp/de import { resolveWorkflowIdForUser } from '@/lib/workflows/utils' const logger = createLogger('CopilotMcpAPI') +const mcpRateLimiter = new RateLimiter() export const dynamic = 'force-dynamic' export const runtime = 'nodejs' +interface CopilotKeyAuthResult { + success: boolean + userId?: string + error?: string +} + +/** + * Validates a copilot API key by forwarding it to the Go copilot service's + * `/api/validate-key` endpoint. Returns the associated userId on success. + */ +async function authenticateCopilotApiKey(apiKey: string): Promise { + try { + const internalSecret = env.INTERNAL_API_SECRET + if (!internalSecret) { + logger.error('INTERNAL_API_SECRET not configured') + return { success: false, error: 'Server configuration error' } + } + + const res = await fetch(`${SIM_AGENT_API_URL}/api/validate-key`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': internalSecret, + }, + body: JSON.stringify({ targetApiKey: apiKey }), + signal: AbortSignal.timeout(10_000), + }) + + if (!res.ok) { + const body = await res.json().catch(() => null) + const upstream = (body as Record)?.message + const status = res.status + + if (status === 401 || status === 403) { + return { + success: false, + error: `Invalid Copilot API key. Generate a new key in Settings → Copilot and set it in the x-api-key header.`, + } + } + if (status === 402) { + return { + success: false, + error: `Usage limit exceeded for this Copilot API key. Upgrade your plan or wait for your quota to reset.`, + } + } + + return { success: false, error: String(upstream ?? 'Copilot API key validation failed') } + } + + const data = (await res.json()) as { ok?: boolean; userId?: string } + if (!data.ok || !data.userId) { + return { + success: false, + error: 'Invalid Copilot API key. Generate a new key in Settings → Copilot.', + } + } + + return { success: true, userId: data.userId } + } catch (error) { + logger.error('Copilot API key validation failed', { error }) + return { + success: false, + error: 'Could not validate Copilot API key — the authentication service is temporarily unreachable. This is NOT a problem with the API key itself; please retry shortly.', + } + } +} + /** * MCP Server instructions that guide LLMs on how to use the Sim copilot tools. * This is included in the initialize response to help external LLMs understand @@ -326,37 +395,48 @@ function buildMcpServer(): Server { const apiKeyHeader = readHeader(headers, 'x-api-key') if (!apiKeyHeader) { - throw new McpError( - -32000, - 'API key required. Set the x-api-key header with a valid Sim API key.' - ) + return { + content: [ + { + type: 'text' as const, + text: 'AUTHENTICATION ERROR: No Copilot API key provided. The user must set their Copilot API key in the x-api-key header. They can generate one in the Sim app under Settings → Copilot. Do NOT retry — this will fail until the key is configured.', + }, + ], + isError: true, + } } - const authResult = await authenticateApiKeyFromHeader(apiKeyHeader) + const authResult = await authenticateCopilotApiKey(apiKeyHeader) if (!authResult.success || !authResult.userId) { - logger.warn('MCP auth failed', { - error: authResult.error, - method: request.method, - }) - - throw new McpError(-32000, authResult.error || 'Invalid API key') + logger.warn('MCP copilot key auth failed', { method: request.method }) + return { + content: [ + { + type: 'text' as const, + text: `AUTHENTICATION ERROR: ${authResult.error} Do NOT retry — this will fail until the user fixes their Copilot API key.`, + }, + ], + isError: true, + } } - if (authResult.keyId) { - updateApiKeyLastUsed(authResult.keyId).catch((error) => { - logger.warn('Failed to update API key last-used timestamp', { - keyId: authResult.keyId, - error: error instanceof Error ? error.message : String(error), - }) - }) - } + const rateLimitResult = await mcpRateLimiter.checkRateLimitWithSubscription( + authResult.userId, + await getHighestPrioritySubscription(authResult.userId), + 'api-endpoint', + false + ) - const usageCheck = await checkServerSideUsageLimits(authResult.userId) - if (usageCheck.isExceeded) { - throw new McpError( - -32000, - `Usage limit exceeded: ${usageCheck.message || 'Upgrade your plan.'}` - ) + if (!rateLimitResult.allowed) { + return { + content: [ + { + type: 'text' as const, + text: `RATE LIMIT: Too many requests. Please wait and retry after ${rateLimitResult.resetAt.toISOString()}.`, + }, + ], + isError: true, + } } const params = request.params as { name?: string; arguments?: Record } | undefined diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts index b98af5dd2..faff318f9 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts @@ -246,6 +246,7 @@ export function getCommandDisplayLabel(commandId: string): string { * Model configuration options */ export const MODEL_OPTIONS = [ + { value: 'claude-4.6-opus', label: 'Claude 4.6 Opus' }, { value: 'claude-4.5-opus', label: 'Claude 4.5 Opus' }, { value: 'claude-4.5-sonnet', label: 'Claude 4.5 Sonnet' }, { value: 'claude-4.5-haiku', label: 'Claude 4.5 Haiku' }, diff --git a/apps/sim/lib/copilot/config.ts b/apps/sim/lib/copilot/config.ts index f50b09088..9adb15974 100644 --- a/apps/sim/lib/copilot/config.ts +++ b/apps/sim/lib/copilot/config.ts @@ -108,14 +108,14 @@ function parseBooleanEnv(value: string | undefined): boolean | null { export const DEFAULT_COPILOT_CONFIG: CopilotConfig = { chat: { defaultProvider: 'anthropic', - defaultModel: 'claude-4.5-opus', + defaultModel: 'claude-4.6-opus', temperature: 0.1, maxTokens: 8192, systemPrompt: AGENT_MODE_SYSTEM_PROMPT, }, rag: { defaultProvider: 'anthropic', - defaultModel: 'claude-4.5-opus', + defaultModel: 'claude-4.6-opus', temperature: 0.1, maxTokens: 2000, embeddingModel: 'text-embedding-3-small', diff --git a/apps/sim/lib/copilot/models.ts b/apps/sim/lib/copilot/models.ts index 83a90169b..90d43f1b0 100644 --- a/apps/sim/lib/copilot/models.ts +++ b/apps/sim/lib/copilot/models.ts @@ -18,6 +18,7 @@ export const COPILOT_MODEL_IDS = [ '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', diff --git a/apps/sim/lib/core/config/feature-flags.ts b/apps/sim/lib/core/config/feature-flags.ts index 9f746c5b1..6e65bebd4 100644 --- a/apps/sim/lib/core/config/feature-flags.ts +++ b/apps/sim/lib/core/config/feature-flags.ts @@ -21,9 +21,9 @@ export const isTest = env.NODE_ENV === 'test' /** * Is this the hosted version of the application */ -export const isHosted = - getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.sim.ai' || - getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.staging.sim.ai' +export const isHosted = true + // getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.sim.ai' || + // getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.staging.sim.ai' /** * Is billing enforcement enabled diff --git a/apps/sim/stores/panel/copilot/store.ts b/apps/sim/stores/panel/copilot/store.ts index c3ade2805..6c4c867a9 100644 --- a/apps/sim/stores/panel/copilot/store.ts +++ b/apps/sim/stores/panel/copilot/store.ts @@ -873,7 +873,7 @@ async function resumeFromLiveStream( // Initial state (subset required for UI/streaming) const initialState = { mode: 'build' as const, - selectedModel: 'claude-4.5-opus' as CopilotStore['selectedModel'], + selectedModel: 'claude-4.6-opus' as CopilotStore['selectedModel'], agentPrefetch: false, enabledModels: null as string[] | null, // Null means not loaded yet, empty array means all disabled isCollapsed: false,