Skip to content

Commit f077751

Browse files
Sg312waleedlatif1claudelakeesivicecrasher321
authored
fix(mothership): file materialization tools (#3586)
* Fix ope * File upload fixes * Fix lint * Materialization shows up * Snapshot * Fix * Nuke migrations * Add migs * migs --------- Co-authored-by: Waleed <walif6@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai> Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
1 parent 75bdf46 commit f077751

File tree

19 files changed

+14049
-52
lines changed

19 files changed

+14049
-52
lines changed

apps/sim/app/api/files/authorization.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ export async function verifyFileAccess(
120120
return true
121121
}
122122

123-
// 1. Workspace files: Check database first (most reliable for both local and cloud)
124-
if (inferredContext === 'workspace') {
123+
// 1. Workspace / mothership files: Check database first (most reliable for both local and cloud)
124+
if (inferredContext === 'workspace' || inferredContext === 'mothership') {
125125
return await verifyWorkspaceFileAccess(cloudKey, userId, customConfig, isLocal)
126126
}
127127

apps/sim/app/api/files/upload/route.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { sanitizeFileName } from '@/executor/constants'
44
import '@/lib/uploads/core/setup.server'
55
import { getSession } from '@/lib/auth'
66
import type { StorageContext } from '@/lib/uploads/config'
7+
import { generateWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
78
import { isImageFileType, resolveFileType } from '@/lib/uploads/utils/file-utils'
89
import {
910
SUPPORTED_AUDIO_EXTENSIONS,
@@ -232,6 +233,53 @@ export async function POST(request: NextRequest) {
232233
}
233234
}
234235

236+
// Handle mothership context (chat-scoped uploads to workspace S3)
237+
if (context === 'mothership') {
238+
if (!workspaceId) {
239+
throw new InvalidRequestError('Mothership context requires workspaceId parameter')
240+
}
241+
242+
logger.info(`Uploading mothership file: ${originalName}`)
243+
244+
const storageKey = generateWorkspaceFileKey(workspaceId, originalName)
245+
246+
const metadata: Record<string, string> = {
247+
originalName: originalName,
248+
uploadedAt: new Date().toISOString(),
249+
purpose: 'mothership',
250+
userId: session.user.id,
251+
workspaceId,
252+
}
253+
254+
const fileInfo = await storageService.uploadFile({
255+
file: buffer,
256+
fileName: storageKey,
257+
contentType: file.type || 'application/octet-stream',
258+
context: 'mothership',
259+
preserveKey: true,
260+
customKey: storageKey,
261+
metadata,
262+
})
263+
264+
const finalPath = usingCloudStorage ? `${fileInfo.path}?context=mothership` : fileInfo.path
265+
266+
uploadResults.push({
267+
fileName: originalName,
268+
presignedUrl: '',
269+
fileInfo: {
270+
path: finalPath,
271+
key: fileInfo.key,
272+
name: originalName,
273+
size: buffer.length,
274+
type: file.type || 'application/octet-stream',
275+
},
276+
directUploadSupported: false,
277+
})
278+
279+
logger.info(`Successfully uploaded mothership file: ${fileInfo.key}`)
280+
continue
281+
}
282+
235283
// Handle copilot, chat, profile-pictures contexts
236284
if (context === 'copilot' || context === 'chat' || context === 'profile-pictures') {
237285
if (context === 'copilot') {

apps/sim/app/api/mothership/chat/route.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const FileAttachmentSchema = z.object({
3131
const ResourceAttachmentSchema = z.object({
3232
type: z.enum(['workflow', 'table', 'file', 'knowledgebase']),
3333
id: z.string().min(1),
34+
title: z.string().optional(),
35+
active: z.boolean().optional(),
3436
})
3537

3638
const MothershipMessageSchema = z.object({
@@ -124,9 +126,19 @@ export async function POST(req: NextRequest) {
124126

125127
if (Array.isArray(resourceAttachments) && resourceAttachments.length > 0) {
126128
const results = await Promise.allSettled(
127-
resourceAttachments.map((r) =>
128-
resolveActiveResourceContext(r.type, r.id, workspaceId, authenticatedUserId)
129-
)
129+
resourceAttachments.map(async (r) => {
130+
const ctx = await resolveActiveResourceContext(
131+
r.type,
132+
r.id,
133+
workspaceId,
134+
authenticatedUserId
135+
)
136+
if (!ctx) return null
137+
return {
138+
...ctx,
139+
tag: r.active ? '@active_tab' : '@open_tab',
140+
}
141+
})
130142
)
131143
for (const result of results) {
132144
if (result.status === 'fulfilled' && result.value) {

apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ export function UserInput({
212212

213213
const files = useFileAttachments({
214214
userId: userId || session?.user?.id,
215+
workspaceId,
215216
disabled: false,
216217
isLoading: isSending,
217218
})

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -880,12 +880,15 @@ export function useChat(
880880
try {
881881
const currentActiveId = activeResourceIdRef.current
882882
const currentResources = resourcesRef.current
883-
const activeRes = currentActiveId
884-
? currentResources.find((r) => r.id === currentActiveId)
885-
: undefined
886-
const resourceAttachments = activeRes
887-
? [{ type: activeRes.type, id: activeRes.id }]
888-
: undefined
883+
const resourceAttachments =
884+
currentResources.length > 0
885+
? currentResources.map((r) => ({
886+
type: r.type,
887+
id: r.id,
888+
title: r.title,
889+
active: r.id === currentActiveId,
890+
}))
891+
: undefined
889892

890893
const response = await fetch(MOTHERSHIP_CHAT_API_PATH, {
891894
method: 'POST',

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-file-attachments.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface MessageFileAttachment {
4343

4444
interface UseFileAttachmentsProps {
4545
userId?: string
46+
workspaceId?: string
4647
disabled?: boolean
4748
isLoading?: boolean
4849
}
@@ -55,7 +56,7 @@ interface UseFileAttachmentsProps {
5556
* @returns File attachment state and operations
5657
*/
5758
export function useFileAttachments(props: UseFileAttachmentsProps) {
58-
const { userId, disabled, isLoading } = props
59+
const { userId, workspaceId, disabled, isLoading } = props
5960

6061
const [attachedFiles, setAttachedFiles] = useState<AttachedFile[]>([])
6162
const [isDragging, setIsDragging] = useState(false)
@@ -135,7 +136,10 @@ export function useFileAttachments(props: UseFileAttachmentsProps) {
135136
try {
136137
const formData = new FormData()
137138
formData.append('file', file)
138-
formData.append('context', 'copilot')
139+
formData.append('context', 'mothership')
140+
if (workspaceId) {
141+
formData.append('workspaceId', workspaceId)
142+
}
139143

140144
const uploadResponse = await fetch('/api/files/upload', {
141145
method: 'POST',
@@ -171,7 +175,7 @@ export function useFileAttachments(props: UseFileAttachmentsProps) {
171175
}
172176
}
173177
},
174-
[userId]
178+
[userId, workspaceId]
175179
)
176180

177181
/**

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/user-input.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ const UserInput = forwardRef<UserInputRef, UserInputProps>(
188188

189189
const fileAttachments = useFileAttachments({
190190
userId: session?.user?.id,
191+
workspaceId,
191192
disabled,
192193
isLoading,
193194
})

apps/sim/lib/copilot/chat-payload.ts

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { createLogger } from '@sim/logger'
22
import { getUserSubscriptionState } from '@/lib/billing/core/subscription'
3-
import { processFileAttachments } from '@/lib/copilot/chat-context'
43
import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions'
54
import { isHosted } from '@/lib/core/config/feature-flags'
65
import { createMcpToolId } from '@/lib/mcp/utils'
6+
import { trackChatUpload } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
77
import { getWorkflowById } from '@/lib/workflows/utils'
88
import { tools } from '@/tools/registry'
99
import { getLatestVersionTools, stripVersionSuffix } from '@/tools/utils'
@@ -126,7 +126,47 @@ export async function buildCopilotRequestPayload(
126126
const effectiveMode = mode === 'agent' ? 'build' : mode
127127
const transportMode = effectiveMode === 'build' ? 'agent' : effectiveMode
128128

129-
const processedFileContents = await processFileAttachments(fileAttachments ?? [], userId)
129+
// Track uploaded files in the DB and build context tags instead of base64 inlining
130+
const uploadContexts: Array<{ type: string; content: string }> = []
131+
if (chatId && params.workspaceId && fileAttachments && fileAttachments.length > 0) {
132+
for (const f of fileAttachments) {
133+
const filename = (f.filename ?? f.name ?? 'file') as string
134+
const mediaType = (f.media_type ?? f.mimeType ?? 'application/octet-stream') as string
135+
try {
136+
await trackChatUpload(
137+
params.workspaceId,
138+
userId,
139+
chatId,
140+
f.key,
141+
filename,
142+
mediaType,
143+
f.size
144+
)
145+
const lines = [
146+
`File "${filename}" (${mediaType}, ${f.size} bytes) uploaded.`,
147+
`Read with: read("uploads/${filename}")`,
148+
`To save permanently: materialize_file(fileName: "${filename}")`,
149+
]
150+
if (filename.endsWith('.json')) {
151+
lines.push(
152+
`To import as a workflow: materialize_file(fileName: "${filename}", operation: "import")`
153+
)
154+
}
155+
uploadContexts.push({
156+
type: 'uploaded_file',
157+
content: lines.join('\n'),
158+
})
159+
} catch (err) {
160+
logger.warn('Failed to track chat upload', {
161+
filename,
162+
chatId,
163+
error: err instanceof Error ? err.message : String(err),
164+
})
165+
}
166+
}
167+
}
168+
169+
const allContexts = [...(contexts ?? []), ...uploadContexts]
130170

131171
let integrationTools: ToolSchema[] = []
132172

@@ -170,11 +210,10 @@ export async function buildCopilotRequestPayload(
170210
...(provider ? { provider } : {}),
171211
mode: transportMode,
172212
messageId: userMessageId,
173-
...(contexts && contexts.length > 0 ? { context: contexts } : {}),
213+
...(allContexts.length > 0 ? { context: allContexts } : {}),
174214
...(chatId ? { chatId } : {}),
175215
...(typeof prefetch === 'boolean' ? { prefetch } : {}),
176216
...(implicitFeedback ? { implicitFeedback } : {}),
177-
...(processedFileContents.length > 0 ? { fileAttachments: processedFileContents } : {}),
178217
...(integrationTools.length > 0 ? { integrationTools } : {}),
179218
...(commands && commands.length > 0 ? { commands } : {}),
180219
...(params.workspaceContext ? { workspaceContext: params.workspaceContext } : {}),

apps/sim/lib/copilot/orchestrator/tool-executor/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
executeManageJob,
4848
executeUpdateJobHistory,
4949
} from './job-tools'
50+
import { executeMaterializeFile } from './materialize-file'
5051
import type {
5152
CheckDeploymentStatusParams,
5253
CreateFolderParams,
@@ -984,6 +985,7 @@ const SIM_WORKFLOW_TOOL_HANDLERS: Record<
984985
},
985986
}
986987
},
988+
materialize_file: (p, c) => executeMaterializeFile(p, c),
987989
manage_custom_tool: (p, c) => executeManageCustomTool(p, c),
988990
manage_mcp_tool: (p, c) => executeManageMcpTool(p, c),
989991
manage_skill: (p, c) => executeManageSkill(p, c),

0 commit comments

Comments
 (0)