mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
* feat(logs): add additional metadata for workflow execution logs
* Revert "Feat(logs) upgrade mothership chat messages to error (#3772)"
This reverts commit 9d1b9763c5.
* Fix lint, address greptile comments
* improvement(sidebar): expand sidebar by hovering and clicking the edge (#3830)
* improvement(sidebar): expand sidebar by hovering and clicking the edge
* improvement(sidebar): add keyboard shortcuts for new workflow/task, center search modal, fix edge ARIA
* improvement(sidebar): use Tooltip.Shortcut for inline shortcut display
* fix(sidebar): change new workflow shortcut from Mod+Shift+W to Mod+Shift+P to avoid browser close-window conflict
* fix(hotkeys): fall back to event.code for international keyboard layout compatibility
* fix(sidebar): guard add-workflow shortcut with canEdit and isCreatingWorkflow checks
* feat(ui): handle image paste (#3826)
* feat(ui): handle image paste
* Fix lint
* Fix type error
---------
Co-authored-by: Theodore Li <theo@sim.ai>
* feat(files): interactive markdown checkbox toggling in preview (#3829)
* feat(files): interactive markdown checkbox toggling in preview
* fix(files): handle ordered-list checkboxes and fix index drift
* lint
* fix(files): remove counter offset that prevented checkbox toggling
* fix(files): apply task-list styling to ordered lists too
* fix(files): render single pass when interactive to avoid index drift
* fix(files): move useMemo above conditional return to fix Rules of Hooks
* fix(files): pass content directly to preview when not streaming to avoid stale frame
* improvement(home): position @ mention popup at caret and fix icon consistency (#3831)
* improvement(home): position @ mention popup at caret and fix icon consistency
* fix(home): pin mirror div to document origin and guard button anchor
* chore(auth): restore hybrid.ts to staging
* improvement(ui): sidebar (#3832)
* Fix logger tests
* Add metadata to mothership logs
---------
Co-authored-by: Theodore Li <theo@sim.ai>
Co-authored-by: Waleed <walif6@gmail.com>
Co-authored-by: Theodore Li <theo@sim.ai>
231 lines
7.7 KiB
TypeScript
231 lines
7.7 KiB
TypeScript
import { createLogger } from '@sim/logger'
|
|
import { getUserSubscriptionState } from '@/lib/billing/core/subscription'
|
|
import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions'
|
|
import { isHosted } from '@/lib/core/config/feature-flags'
|
|
import { createMcpToolId } from '@/lib/mcp/utils'
|
|
import { trackChatUpload } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
|
import { getWorkflowById } from '@/lib/workflows/utils'
|
|
import { tools } from '@/tools/registry'
|
|
import { getLatestVersionTools, stripVersionSuffix } from '@/tools/utils'
|
|
|
|
const logger = createLogger('CopilotChatPayload')
|
|
|
|
export interface BuildPayloadParams {
|
|
message: string
|
|
workflowId?: string
|
|
workflowName?: string
|
|
workspaceId?: string
|
|
userId: string
|
|
userMessageId: string
|
|
mode: string
|
|
model: string
|
|
provider?: string
|
|
contexts?: Array<{ type: string; content: string }>
|
|
fileAttachments?: Array<{ id: string; key: string; size: number; [key: string]: unknown }>
|
|
commands?: string[]
|
|
chatId?: string
|
|
prefetch?: boolean
|
|
implicitFeedback?: string
|
|
workspaceContext?: string
|
|
userPermission?: string
|
|
userTimezone?: string
|
|
}
|
|
|
|
export interface ToolSchema {
|
|
name: string
|
|
description: string
|
|
input_schema: Record<string, unknown>
|
|
defer_loading?: boolean
|
|
executeLocally?: boolean
|
|
oauth?: { required: boolean; provider: string }
|
|
}
|
|
|
|
/**
|
|
* Build deferred integration tool schemas from the Sim tool registry.
|
|
* Shared by the interactive chat payload builder and the non-interactive
|
|
* block execution route so both paths send the same tool definitions to Go.
|
|
*/
|
|
export async function buildIntegrationToolSchemas(
|
|
userId: string,
|
|
messageId?: string
|
|
): Promise<ToolSchema[]> {
|
|
const reqLogger = logger.withMetadata({ messageId })
|
|
const integrationTools: ToolSchema[] = []
|
|
try {
|
|
const { createUserToolSchema } = await import('@/tools/params')
|
|
const latestTools = getLatestVersionTools(tools)
|
|
let shouldAppendEmailTagline = false
|
|
|
|
try {
|
|
const subscriptionState = await getUserSubscriptionState(userId)
|
|
shouldAppendEmailTagline = subscriptionState.isFree
|
|
} catch (error) {
|
|
reqLogger.warn('Failed to load subscription state for copilot tool descriptions', {
|
|
userId,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
})
|
|
}
|
|
|
|
for (const [toolId, toolConfig] of Object.entries(latestTools)) {
|
|
try {
|
|
const userSchema = createUserToolSchema(toolConfig)
|
|
const strippedName = stripVersionSuffix(toolId)
|
|
integrationTools.push({
|
|
name: strippedName,
|
|
description: getCopilotToolDescription(toolConfig, {
|
|
isHosted,
|
|
fallbackName: strippedName,
|
|
appendEmailTagline: shouldAppendEmailTagline,
|
|
}),
|
|
input_schema: userSchema as unknown as Record<string, unknown>,
|
|
defer_loading: true,
|
|
...(toolConfig.oauth?.required && {
|
|
oauth: {
|
|
required: true,
|
|
provider: toolConfig.oauth.provider,
|
|
},
|
|
}),
|
|
})
|
|
} catch (toolError) {
|
|
reqLogger.warn('Failed to build schema for tool, skipping', {
|
|
toolId,
|
|
error: toolError instanceof Error ? toolError.message : String(toolError),
|
|
})
|
|
}
|
|
}
|
|
} catch (error) {
|
|
reqLogger.warn('Failed to build tool schemas', {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
})
|
|
}
|
|
return integrationTools
|
|
}
|
|
|
|
/**
|
|
* Build the request payload for the copilot backend.
|
|
*/
|
|
export async function buildCopilotRequestPayload(
|
|
params: BuildPayloadParams,
|
|
options: {
|
|
selectedModel: string
|
|
}
|
|
): Promise<Record<string, unknown>> {
|
|
const {
|
|
message,
|
|
workflowId,
|
|
userId,
|
|
userMessageId,
|
|
mode,
|
|
provider,
|
|
contexts,
|
|
fileAttachments,
|
|
commands,
|
|
chatId,
|
|
prefetch,
|
|
implicitFeedback,
|
|
} = params
|
|
|
|
const selectedModel = options.selectedModel
|
|
|
|
const effectiveMode = mode === 'agent' ? 'build' : mode
|
|
const transportMode = effectiveMode === 'build' ? 'agent' : effectiveMode
|
|
|
|
// Track uploaded files in the DB and build context tags instead of base64 inlining
|
|
const uploadContexts: Array<{ type: string; content: string }> = []
|
|
if (chatId && params.workspaceId && fileAttachments && fileAttachments.length > 0) {
|
|
for (const f of fileAttachments) {
|
|
const filename = (f.filename ?? f.name ?? 'file') as string
|
|
const mediaType = (f.media_type ?? f.mimeType ?? 'application/octet-stream') as string
|
|
try {
|
|
await trackChatUpload(
|
|
params.workspaceId,
|
|
userId,
|
|
chatId,
|
|
f.key,
|
|
filename,
|
|
mediaType,
|
|
f.size
|
|
)
|
|
const lines = [
|
|
`File "${filename}" (${mediaType}, ${f.size} bytes) uploaded.`,
|
|
`Read with: read("uploads/${filename}")`,
|
|
`To save permanently: materialize_file(fileName: "${filename}")`,
|
|
]
|
|
if (filename.endsWith('.json')) {
|
|
lines.push(
|
|
`To import as a workflow: materialize_file(fileName: "${filename}", operation: "import")`
|
|
)
|
|
}
|
|
uploadContexts.push({
|
|
type: 'uploaded_file',
|
|
content: lines.join('\n'),
|
|
})
|
|
} catch (err) {
|
|
logger.warn('Failed to track chat upload', {
|
|
filename,
|
|
chatId,
|
|
error: err instanceof Error ? err.message : String(err),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
const allContexts = [...(contexts ?? []), ...uploadContexts]
|
|
|
|
let integrationTools: ToolSchema[] = []
|
|
|
|
const payloadLogger = logger.withMetadata({ messageId: userMessageId })
|
|
|
|
if (effectiveMode === 'build') {
|
|
integrationTools = await buildIntegrationToolSchemas(userId, userMessageId)
|
|
|
|
// Discover MCP tools from workspace servers and include as deferred tools
|
|
if (workflowId) {
|
|
try {
|
|
const wf = await getWorkflowById(workflowId)
|
|
if (wf?.workspaceId) {
|
|
const { mcpService } = await import('@/lib/mcp/service')
|
|
const mcpTools = await mcpService.discoverTools(userId, wf.workspaceId)
|
|
for (const mcpTool of mcpTools) {
|
|
integrationTools.push({
|
|
name: createMcpToolId(mcpTool.serverId, mcpTool.name),
|
|
description:
|
|
mcpTool.description || `MCP tool: ${mcpTool.name} (${mcpTool.serverName})`,
|
|
input_schema: mcpTool.inputSchema as unknown as Record<string, unknown>,
|
|
})
|
|
}
|
|
if (mcpTools.length > 0) {
|
|
payloadLogger.info('Added MCP tools to copilot payload', { count: mcpTools.length })
|
|
}
|
|
}
|
|
} catch (error) {
|
|
payloadLogger.warn('Failed to discover MCP tools for copilot', {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
message,
|
|
...(workflowId ? { workflowId } : {}),
|
|
...(params.workflowName ? { workflowName: params.workflowName } : {}),
|
|
...(params.workspaceId ? { workspaceId: params.workspaceId } : {}),
|
|
userId,
|
|
...(selectedModel ? { model: selectedModel } : {}),
|
|
...(provider ? { provider } : {}),
|
|
mode: transportMode,
|
|
messageId: userMessageId,
|
|
...(allContexts.length > 0 ? { context: allContexts } : {}),
|
|
...(chatId ? { chatId } : {}),
|
|
...(typeof prefetch === 'boolean' ? { prefetch } : {}),
|
|
...(implicitFeedback ? { implicitFeedback } : {}),
|
|
...(integrationTools.length > 0 ? { integrationTools } : {}),
|
|
...(commands && commands.length > 0 ? { commands } : {}),
|
|
...(params.workspaceContext ? { workspaceContext: params.workspaceContext } : {}),
|
|
...(params.userPermission ? { userPermission: params.userPermission } : {}),
|
|
...(params.userTimezone ? { userTimezone: params.userTimezone } : {}),
|
|
isHosted,
|
|
}
|
|
}
|