mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-06 20:55:23 -05:00
Add opus 4.6
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -28,6 +28,7 @@ const DEFAULT_ENABLED_MODELS: Record<CopilotModelId, boolean> = {
|
||||
'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,
|
||||
|
||||
@@ -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<CopilotKeyAuthResult> {
|
||||
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<string, unknown>)?.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<string, unknown> } | undefined
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user