mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04: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.
|
Select your preferred AI model using the model selector at the bottom right of the input area.
|
||||||
|
|
||||||
**Available Models:**
|
**Available Models:**
|
||||||
- Claude 4.5 Opus, Sonnet (default), Haiku
|
- Claude 4.6 Opus (default), 4.5 Opus, Sonnet, Haiku
|
||||||
- GPT 5.2 Codex, Pro
|
- GPT 5.2 Codex, Pro
|
||||||
- Gemini 3 Pro
|
- Gemini 3 Pro
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const ChatMessageSchema = z.object({
|
|||||||
chatId: z.string().optional(),
|
chatId: z.string().optional(),
|
||||||
workflowId: z.string().optional(),
|
workflowId: z.string().optional(),
|
||||||
workflowName: 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'),
|
mode: z.enum(COPILOT_REQUEST_MODES).optional().default('agent'),
|
||||||
prefetch: z.boolean().optional(),
|
prefetch: z.boolean().optional(),
|
||||||
createNewChat: z.boolean().optional().default(false),
|
createNewChat: z.boolean().optional().default(false),
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const DEFAULT_ENABLED_MODELS: Record<CopilotModelId, boolean> = {
|
|||||||
'claude-4-sonnet': false,
|
'claude-4-sonnet': false,
|
||||||
'claude-4.5-haiku': true,
|
'claude-4.5-haiku': true,
|
||||||
'claude-4.5-sonnet': true,
|
'claude-4.5-sonnet': true,
|
||||||
|
'claude-4.6-opus': true,
|
||||||
'claude-4.5-opus': true,
|
'claude-4.5-opus': true,
|
||||||
'claude-4.1-opus': false,
|
'claude-4.1-opus': false,
|
||||||
'gemini-3-pro': true,
|
'gemini-3-pro': true,
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ import { createLogger } from '@sim/logger'
|
|||||||
import { randomUUID } from 'node:crypto'
|
import { randomUUID } from 'node:crypto'
|
||||||
import { eq, sql } from 'drizzle-orm'
|
import { eq, sql } from 'drizzle-orm'
|
||||||
import { type NextRequest, NextResponse } from 'next/server'
|
import { type NextRequest, NextResponse } from 'next/server'
|
||||||
import { authenticateApiKeyFromHeader, updateApiKeyLastUsed } from '@/lib/api-key/service'
|
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
|
||||||
import { checkServerSideUsageLimits } from '@/lib/billing/calculations/usage-monitor'
|
|
||||||
import { getCopilotModel } from '@/lib/copilot/config'
|
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 { orchestrateCopilotStream } from '@/lib/copilot/orchestrator'
|
||||||
import { orchestrateSubagentStream } from '@/lib/copilot/orchestrator/subagent'
|
import { orchestrateSubagentStream } from '@/lib/copilot/orchestrator/subagent'
|
||||||
import {
|
import {
|
||||||
@@ -30,10 +31,78 @@ import { DIRECT_TOOL_DEFS, SUBAGENT_TOOL_DEFS } from '@/lib/copilot/tools/mcp/de
|
|||||||
import { resolveWorkflowIdForUser } from '@/lib/workflows/utils'
|
import { resolveWorkflowIdForUser } from '@/lib/workflows/utils'
|
||||||
|
|
||||||
const logger = createLogger('CopilotMcpAPI')
|
const logger = createLogger('CopilotMcpAPI')
|
||||||
|
const mcpRateLimiter = new RateLimiter()
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
export const runtime = 'nodejs'
|
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.
|
* 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
|
* 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')
|
const apiKeyHeader = readHeader(headers, 'x-api-key')
|
||||||
|
|
||||||
if (!apiKeyHeader) {
|
if (!apiKeyHeader) {
|
||||||
throw new McpError(
|
return {
|
||||||
-32000,
|
content: [
|
||||||
'API key required. Set the x-api-key header with a valid Sim API key.'
|
{
|
||||||
)
|
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) {
|
if (!authResult.success || !authResult.userId) {
|
||||||
logger.warn('MCP auth failed', {
|
logger.warn('MCP copilot key auth failed', { method: request.method })
|
||||||
error: authResult.error,
|
return {
|
||||||
method: request.method,
|
content: [
|
||||||
})
|
{
|
||||||
|
type: 'text' as const,
|
||||||
throw new McpError(-32000, authResult.error || 'Invalid API key')
|
text: `AUTHENTICATION ERROR: ${authResult.error} Do NOT retry — this will fail until the user fixes their Copilot API key.`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authResult.keyId) {
|
const rateLimitResult = await mcpRateLimiter.checkRateLimitWithSubscription(
|
||||||
updateApiKeyLastUsed(authResult.keyId).catch((error) => {
|
authResult.userId,
|
||||||
logger.warn('Failed to update API key last-used timestamp', {
|
await getHighestPrioritySubscription(authResult.userId),
|
||||||
keyId: authResult.keyId,
|
'api-endpoint',
|
||||||
error: error instanceof Error ? error.message : String(error),
|
false
|
||||||
})
|
)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const usageCheck = await checkServerSideUsageLimits(authResult.userId)
|
if (!rateLimitResult.allowed) {
|
||||||
if (usageCheck.isExceeded) {
|
return {
|
||||||
throw new McpError(
|
content: [
|
||||||
-32000,
|
{
|
||||||
`Usage limit exceeded: ${usageCheck.message || 'Upgrade your plan.'}`
|
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
|
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
|
* Model configuration options
|
||||||
*/
|
*/
|
||||||
export const MODEL_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-opus', label: 'Claude 4.5 Opus' },
|
||||||
{ value: 'claude-4.5-sonnet', label: 'Claude 4.5 Sonnet' },
|
{ value: 'claude-4.5-sonnet', label: 'Claude 4.5 Sonnet' },
|
||||||
{ value: 'claude-4.5-haiku', label: 'Claude 4.5 Haiku' },
|
{ 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 = {
|
export const DEFAULT_COPILOT_CONFIG: CopilotConfig = {
|
||||||
chat: {
|
chat: {
|
||||||
defaultProvider: 'anthropic',
|
defaultProvider: 'anthropic',
|
||||||
defaultModel: 'claude-4.5-opus',
|
defaultModel: 'claude-4.6-opus',
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
maxTokens: 8192,
|
maxTokens: 8192,
|
||||||
systemPrompt: AGENT_MODE_SYSTEM_PROMPT,
|
systemPrompt: AGENT_MODE_SYSTEM_PROMPT,
|
||||||
},
|
},
|
||||||
rag: {
|
rag: {
|
||||||
defaultProvider: 'anthropic',
|
defaultProvider: 'anthropic',
|
||||||
defaultModel: 'claude-4.5-opus',
|
defaultModel: 'claude-4.6-opus',
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
maxTokens: 2000,
|
maxTokens: 2000,
|
||||||
embeddingModel: 'text-embedding-3-small',
|
embeddingModel: 'text-embedding-3-small',
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const COPILOT_MODEL_IDS = [
|
|||||||
'claude-4-sonnet',
|
'claude-4-sonnet',
|
||||||
'claude-4.5-haiku',
|
'claude-4.5-haiku',
|
||||||
'claude-4.5-sonnet',
|
'claude-4.5-sonnet',
|
||||||
|
'claude-4.6-opus',
|
||||||
'claude-4.5-opus',
|
'claude-4.5-opus',
|
||||||
'claude-4.1-opus',
|
'claude-4.1-opus',
|
||||||
'gemini-3-pro',
|
'gemini-3-pro',
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ export const isTest = env.NODE_ENV === 'test'
|
|||||||
/**
|
/**
|
||||||
* Is this the hosted version of the application
|
* Is this the hosted version of the application
|
||||||
*/
|
*/
|
||||||
export const isHosted =
|
export const isHosted = true
|
||||||
getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.sim.ai' ||
|
// getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.sim.ai' ||
|
||||||
getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.staging.sim.ai'
|
// getEnv('NEXT_PUBLIC_APP_URL') === 'https://www.staging.sim.ai'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is billing enforcement enabled
|
* Is billing enforcement enabled
|
||||||
|
|||||||
@@ -873,7 +873,7 @@ async function resumeFromLiveStream(
|
|||||||
// Initial state (subset required for UI/streaming)
|
// Initial state (subset required for UI/streaming)
|
||||||
const initialState = {
|
const initialState = {
|
||||||
mode: 'build' as const,
|
mode: 'build' as const,
|
||||||
selectedModel: 'claude-4.5-opus' as CopilotStore['selectedModel'],
|
selectedModel: 'claude-4.6-opus' as CopilotStore['selectedModel'],
|
||||||
agentPrefetch: false,
|
agentPrefetch: false,
|
||||||
enabledModels: null as string[] | null, // Null means not loaded yet, empty array means all disabled
|
enabledModels: null as string[] | null, // Null means not loaded yet, empty array means all disabled
|
||||||
isCollapsed: false,
|
isCollapsed: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user