Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/sim/app/api/mcp/copilot/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { resolveWorkflowIdForUser } from '@/lib/workflows/utils'

const logger = createLogger('CopilotMcpAPI')
const mcpRateLimiter = new RateLimiter()
const DEFAULT_COPILOT_MODEL = 'claude-opus-4-6'
const DEFAULT_COPILOT_MODEL = 'claude-opus-4-8'

export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'
Expand Down
30 changes: 29 additions & 1 deletion apps/sim/lib/copilot/chat/payload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ vi.mock('@/tools/params', () => ({
createUserToolSchema: mockCreateUserToolSchema,
}))

import { buildIntegrationToolSchemas } from './payload'
import { buildCopilotRequestPayload, buildIntegrationToolSchemas } from './payload'

describe('buildIntegrationToolSchemas', () => {
beforeEach(() => {
Expand Down Expand Up @@ -123,3 +123,31 @@ describe('buildIntegrationToolSchemas', () => {
)
})
})

describe('buildCopilotRequestPayload', () => {
beforeEach(() => {
vi.clearAllMocks()
})

it('passes workspaceContext through to the Go request payload', async () => {
const payload = await buildCopilotRequestPayload(
{
message: 'debug workspace',
userId: 'user-1',
userMessageId: 'msg-1',
mode: 'agent',
model: 'claude-opus-4-8',
workspaceId: 'ws-1',
workspaceContext: 'workspace inventory',
},
{ selectedModel: 'claude-opus-4-8' }
)

expect(payload).toEqual(
expect.objectContaining({
workspaceId: 'ws-1',
workspaceContext: 'workspace inventory',
})
)
})
})
10 changes: 6 additions & 4 deletions apps/sim/lib/copilot/chat/post.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,17 @@ describe('handleUnifiedChatPost', () => {
)

expect(response.status).toBe(200)
expect(generateWorkspaceContext).toHaveBeenCalledWith('ws-1', 'user-1')
expect(buildCopilotRequestPayload).toHaveBeenCalledWith(
expect.objectContaining({
model: 'claude-opus-4-7',
model: 'claude-opus-4-8',
workspaceContext: 'workspace context',
}),
{ selectedModel: 'claude-opus-4-7' }
{ selectedModel: 'claude-opus-4-8' }
)
expect(createSSEStream).toHaveBeenCalledWith(
expect.objectContaining({
titleModel: 'claude-opus-4-7',
titleModel: 'claude-opus-4-8',
workspaceId: 'ws-1',
orchestrateOptions: expect.objectContaining({
workflowId: 'wf-1',
Expand Down Expand Up @@ -213,7 +215,7 @@ describe('handleUnifiedChatPost', () => {
)
expect(createSSEStream).toHaveBeenCalledWith(
expect.objectContaining({
titleModel: 'claude-opus-4-7',
titleModel: 'claude-opus-4-8',
workspaceId: 'ws-1',
orchestrateOptions: expect.objectContaining({
workspaceId: 'ws-1',
Expand Down
13 changes: 8 additions & 5 deletions apps/sim/lib/copilot/chat/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import type { ChatContext } from '@/stores/panel'
export const maxDuration = 3600

const logger = createLogger('UnifiedChatAPI')
const DEFAULT_MODEL = 'claude-opus-4-7'
const DEFAULT_MODEL = 'claude-opus-4-8'

const FileAttachmentSchema = z.object({
id: z.string(),
Expand Down Expand Up @@ -172,6 +172,7 @@ type UnifiedChatBranch =
commands?: string[]
prefetch?: boolean
implicitFeedback?: string
workspaceContext?: string
}) => Promise<Record<string, unknown>>
buildExecutionContext: (params: {
userId: string
Expand Down Expand Up @@ -556,7 +557,7 @@ async function resolveBranch(params: {
workflowId: resolvedWorkflowId,
workflowName: resolved.workflowName,
workspaceId: resolvedWorkspaceId,
effectiveModel: selectedModel,
effectiveModel: selectedModel,
selectedModel,
mode: mode ?? 'agent',
provider,
Expand All @@ -582,6 +583,7 @@ async function resolveBranch(params: {
chatId: payloadParams.chatId,
prefetch: payloadParams.prefetch,
implicitFeedback: payloadParams.implicitFeedback,
workspaceContext: payloadParams.workspaceContext,
userPermission: payloadParams.userPermission,
userTimezone: payloadParams.userTimezone,
},
Expand Down Expand Up @@ -852,11 +854,11 @@ export async function handleUnifiedChatPost(req: NextRequest) {
// apparent "gap" before the model call. Each promise is its own
// span; they run concurrently under Promise.all below.
const workspaceContextPromise =
branch.kind === 'workspace'
workspaceId
? withCopilotSpan(
TraceSpan.CopilotChatBuildWorkspaceContext,
{ [TraceAttr.WorkspaceId]: branch.workspaceId },
() => generateWorkspaceContext(branch.workspaceId, authenticatedUserId),
{ [TraceAttr.WorkspaceId]: workspaceId },
() => generateWorkspaceContext(workspaceId, authenticatedUserId),
activeOtelRoot.context
)
: Promise.resolve(undefined)
Expand Down Expand Up @@ -950,6 +952,7 @@ export async function handleUnifiedChatPost(req: NextRequest) {
commands: body.commands,
prefetch: body.prefetch,
implicitFeedback: body.implicitFeedback,
workspaceContext,
})
: branch.buildPayload({
message: body.message,
Expand Down
9 changes: 6 additions & 3 deletions apps/sim/lib/copilot/tools/handlers/function-execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
import { decodeVfsPathSegments, encodeVfsPathSegments } from '@/lib/copilot/vfs/path-utils'
import { resolveWorkflowAliasForWorkspace } from '@/lib/copilot/vfs/workflow-alias-resolver'
import { isPlanAliasPath, workflowAliasSandboxPath } from '@/lib/copilot/vfs/workflow-aliases'
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/feature-flags'
import { getTableById, listTables, queryRows } from '@/lib/table/service'
import { listWorkspaceFileFolders } from '@/lib/uploads/contexts/workspace/workspace-file-folder-manager'
import {
Expand Down Expand Up @@ -71,7 +72,9 @@ async function resolveInputFiles(
let totalSize = 0

if (inputFiles?.length && workspaceId) {
const allFiles = await listWorkspaceFiles(workspaceId, { includeReservedSystemFiles: true })
const allFiles = await listWorkspaceFiles(workspaceId, {
includeReservedSystemFiles: isMothershipBetaFeaturesEnabled,
})
for (const fileRef of inputFiles) {
const filePath =
typeof fileRef === 'string'
Expand Down Expand Up @@ -124,11 +127,11 @@ async function resolveInputFiles(

if (inputDirectories?.length && workspaceId) {
const folders = await listWorkspaceFileFolders(workspaceId, {
includeReservedSystemFolders: true,
includeReservedSystemFolders: isMothershipBetaFeaturesEnabled,
})
const allFiles = await listWorkspaceFiles(workspaceId, {
folders,
includeReservedSystemFiles: true,
includeReservedSystemFiles: isMothershipBetaFeaturesEnabled,
})
for (const dirRef of inputDirectories) {
const dirPath =
Expand Down
4 changes: 4 additions & 0 deletions apps/sim/lib/copilot/tools/server/files/touch-plan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ vi.mock('@/lib/copilot/vfs/resource-writer', () => ({
writeWorkspaceFileByPath: mocks.writeWorkspaceFileByPath,
}))

vi.mock('@/lib/core/config/feature-flags', () => ({
isMothershipBetaFeaturesEnabled: true,
}))

import { touchPlanServerTool } from './touch-plan'

describe('touch_plan server tool', () => {
Expand Down
4 changes: 4 additions & 0 deletions apps/sim/lib/copilot/tools/server/files/touch-plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@/lib/copilot/vfs/path-utils'
import { writeWorkspaceFileByPath } from '@/lib/copilot/vfs/resource-writer'
import { resolveWorkflowAliasForWorkspace } from '@/lib/copilot/vfs/workflow-alias-resolver'
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/feature-flags'

const logger = createLogger('TouchPlanServerTool')
const TOUCH_PLAN_TOOL_ID = 'touch_plan'
Expand Down Expand Up @@ -64,6 +65,9 @@ function normalizePlanRelativePath(name: string): string {
export const touchPlanServerTool: BaseServerTool<TouchPlanArgs, TouchPlanResult> = {
name: TOUCH_PLAN_TOOL_ID,
async execute(params: TouchPlanArgs, context?: ServerToolContext): Promise<TouchPlanResult> {
if (!isMothershipBetaFeaturesEnabled) {
return { success: false, message: 'touch_plan is not available' }
}
if (!context?.userId) {
throw new Error('Authentication required')
}
Expand Down
16 changes: 13 additions & 3 deletions apps/sim/lib/copilot/tools/server/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
type BaseServerTool,
type ServerToolContext,
} from '@/lib/copilot/tools/server/base-tool'
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/feature-flags'
import { getBlocksMetadataServerTool } from '@/lib/copilot/tools/server/blocks/get-blocks-metadata-tool'
import { getTriggerBlocksServerTool } from '@/lib/copilot/tools/server/blocks/get-trigger-blocks'
import { searchDocumentationServerTool } from '@/lib/copilot/tools/server/docs/search-documentation'
Expand Down Expand Up @@ -130,7 +131,7 @@ function isWriteAction(toolName: string, action: string | undefined): boolean {
}

/** Registry of all server tools. Tools self-declare their validation schemas. */
const serverToolRegistry: Record<string, BaseServerTool> = {
const baseServerToolRegistry: Record<string, BaseServerTool> = {
[getBlocksMetadataServerTool.name]: getBlocksMetadataServerTool,
[getTriggerBlocksServerTool.name]: getTriggerBlocksServerTool,
[editWorkflowServerTool.name]: editWorkflowServerTool,
Expand Down Expand Up @@ -159,16 +160,25 @@ const serverToolRegistry: Record<string, BaseServerTool> = {
[generateImageServerTool.name]: generateImageServerTool,
}

function getServerToolRegistry(): Record<string, BaseServerTool> {
if (isMothershipBetaFeaturesEnabled) {
return baseServerToolRegistry
}
const registry = { ...baseServerToolRegistry }
delete registry[touchPlanServerTool.name]
return registry
}

export function getRegisteredServerToolNames(): string[] {
return Object.keys(serverToolRegistry)
return Object.keys(getServerToolRegistry())
}
Comment on lines +163 to 174
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Since isMothershipBetaFeaturesEnabled is evaluated once at module load and never changes, getServerToolRegistry() builds a new shallow copy of the registry object on every call to routeExecution and getRegisteredServerToolNames. The copy is cheap, but it's unnecessary allocation on every tool invocation. Computing the registry once at startup avoids this entirely.

Suggested change
function getServerToolRegistry(): Record<string, BaseServerTool> {
if (isMothershipBetaFeaturesEnabled) {
return baseServerToolRegistry
}
const registry = { ...baseServerToolRegistry }
delete registry[touchPlanServerTool.name]
return registry
}
export function getRegisteredServerToolNames(): string[] {
return Object.keys(serverToolRegistry)
return Object.keys(getServerToolRegistry())
}
const serverToolRegistry: Record<string, BaseServerTool> = (() => {
if (isMothershipBetaFeaturesEnabled) {
return baseServerToolRegistry
}
const registry = { ...baseServerToolRegistry }
delete registry[touchPlanServerTool.name]
return registry
})()
export function getRegisteredServerToolNames(): string[] {
return Object.keys(serverToolRegistry)
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


export async function routeExecution(
toolName: string,
payload: unknown,
context?: ServerToolContext
): Promise<unknown> {
const tool = serverToolRegistry[toolName]
const tool = getServerToolRegistry()[toolName]
if (!tool) {
throw new Error(`Unknown server tool: ${toolName}`)
}
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/lib/copilot/vfs/workflow-alias-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import {
resolveWorkflowAliasPath,
type WorkflowAliasTarget,
} from '@/lib/copilot/vfs/workflow-aliases'
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/feature-flags'
import { canonicalizeVfsPath } from './path-utils'

export async function resolveWorkflowAliasForWorkspace(args: {
workspaceId: string
path: string
}): Promise<WorkflowAliasTarget | null> {
if (!isMothershipBetaFeaturesEnabled) return null
if (!isPlanAliasPath(args.path)) return null

let canonicalPath: string
Expand Down
Loading
Loading