Skip to content
Prev Previous commit
Next Next commit
--wip-- [skip ci]
  • Loading branch information
Theodore Li committed Mar 19, 2026
commit 031e12fffdbf0374ac3d43167859c16054a8a384
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,11 @@ import {
updateWorkspaceFileContent,
uploadWorkspaceFile,
} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'

export const dynamic = 'force-dynamic'

const logger = createLogger('SimFileManageAPI')

const EXT_TO_MIME: Record<string, string> = {
'.txt': 'text/plain',
'.md': 'text/markdown',
'.html': 'text/html',
'.json': 'application/json',
'.csv': 'text/csv',
'.xml': 'application/xml',
'.yaml': 'application/x-yaml',
'.yml': 'application/x-yaml',
}

function inferContentType(fileName: string, explicitType?: string): string {
if (explicitType) return explicitType
const ext = fileName.slice(fileName.lastIndexOf('.')).toLowerCase()
return EXT_TO_MIME[ext] || 'text/plain'
}
const logger = createLogger('FileManageAPI')

export async function POST(request: NextRequest) {
const auth = await checkInternalAuth(request, { requireWorkflowId: false })
Expand Down Expand Up @@ -101,7 +85,7 @@ export async function POST(request: NextRequest) {
})
}

const mimeType = inferContentType(fileName, contentType)
const mimeType = contentType || getMimeTypeFromExtension(getFileExtension(fileName))
const fileBuffer = Buffer.from(content ?? '', 'utf-8')
const result = await uploadWorkspaceFile(
workspaceId,
Expand Down Expand Up @@ -144,7 +128,7 @@ export async function POST(request: NextRequest) {
const fileBuffer = Buffer.from(finalContent, 'utf-8')
await updateWorkspaceFileContent(workspaceId, fileId, userId, fileBuffer)

logger.info('Sim file written', {
logger.info('File written', {
fileId,
name: fileRecord.name,
size: fileBuffer.length,
Expand Down Expand Up @@ -174,7 +158,7 @@ export async function POST(request: NextRequest) {
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'
logger.error('Sim file operation failed', { operation, error: message })
logger.error('File operation failed', { operation, error: message })
return NextResponse.json({ success: false, error: message }, { status: 500 })
}
}
17 changes: 2 additions & 15 deletions apps/sim/lib/copilot/tools/server/files/workspace-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,10 @@ import {
updateWorkspaceFileContent,
uploadWorkspaceFile,
} from '@/lib/uploads/contexts/workspace/workspace-file-manager'
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'

const logger = createLogger('WorkspaceFileServerTool')

const EXT_TO_MIME: Record<string, string> = {
'.txt': 'text/plain',
'.md': 'text/markdown',
'.html': 'text/html',
'.json': 'application/json',
'.csv': 'text/csv',
}

function inferContentType(fileName: string, explicitType?: string): string {
if (explicitType) return explicitType
const ext = fileName.slice(fileName.lastIndexOf('.')).toLowerCase()
return EXT_TO_MIME[ext] || 'text/plain'
}

export const workspaceFileServerTool: BaseServerTool<WorkspaceFileArgs, WorkspaceFileResult> = {
name: 'workspace_file',
async execute(
Expand Down Expand Up @@ -58,7 +45,7 @@ export const workspaceFileServerTool: BaseServerTool<WorkspaceFileArgs, Workspac
return { success: false, message: 'content is required for write operation' }
}

const contentType = inferContentType(fileName, explicitType)
const contentType = explicitType || getMimeTypeFromExtension(getFileExtension(fileName))
const fileBuffer = Buffer.from(content, 'utf-8')
const result = await uploadWorkspaceFile(
workspaceId,
Expand Down
92 changes: 42 additions & 50 deletions apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,76 +251,68 @@ export async function trackChatUpload(
}

/**
* Check if a file with the same name already exists in workspace
* Check if a file with the same name already exists in workspace.
* Throws on DB errors so callers can distinguish "not found" from "lookup failed."
*/
export async function fileExistsInWorkspace(
workspaceId: string,
fileName: string
): Promise<boolean> {
try {
Comment thread
TheodoreSpeaks marked this conversation as resolved.
const existing = await db
.select()
.from(workspaceFiles)
.where(
and(
eq(workspaceFiles.workspaceId, workspaceId),
eq(workspaceFiles.originalName, fileName),
eq(workspaceFiles.context, 'workspace'),
isNull(workspaceFiles.deletedAt)
)
const existing = await db
.select()
.from(workspaceFiles)
.where(
and(
eq(workspaceFiles.workspaceId, workspaceId),
eq(workspaceFiles.originalName, fileName),
eq(workspaceFiles.context, 'workspace'),
isNull(workspaceFiles.deletedAt)
)
.limit(1)
)
.limit(1)

return existing.length > 0
} catch (error) {
logger.error(`Failed to check file existence for ${fileName}:`, error)
return false
}
return existing.length > 0
}

/**
* Look up a single active workspace file by its original name.
* Returns the record if found, or null otherwise.
* Returns the record if found, or null if no matching file exists.
* Throws on DB errors so callers can distinguish "not found" from "lookup failed."
*/
export async function getWorkspaceFileByName(
workspaceId: string,
fileName: string
): Promise<WorkspaceFileRecord | null> {
try {
const files = await db
.select()
.from(workspaceFiles)
.where(
and(
eq(workspaceFiles.workspaceId, workspaceId),
eq(workspaceFiles.originalName, fileName),
eq(workspaceFiles.context, 'workspace'),
isNull(workspaceFiles.deletedAt)
)
const files = await db
.select()
.from(workspaceFiles)
.where(
and(
eq(workspaceFiles.workspaceId, workspaceId),
eq(workspaceFiles.originalName, fileName),
eq(workspaceFiles.context, 'workspace'),
isNull(workspaceFiles.deletedAt)
)
.limit(1)
)
.limit(1)

if (files.length === 0) return null
if (files.length === 0) return null

const { getServePathPrefix } = await import('@/lib/uploads')
const pathPrefix = getServePathPrefix()
const { getServePathPrefix } = await import('@/lib/uploads')
const pathPrefix = getServePathPrefix()

const file = files[0]
return {
id: file.id,
workspaceId: file.workspaceId || workspaceId,
name: file.originalName,
key: file.key,
path: `${pathPrefix}${encodeURIComponent(file.key)}?context=workspace`,
size: file.size,
type: file.contentType,
uploadedBy: file.userId,
deletedAt: file.deletedAt,
uploadedAt: file.uploadedAt,
}
} catch (error) {
logger.error(`Failed to get workspace file by name "${fileName}":`, error)
return null
const file = files[0]
return {
id: file.id,
workspaceId: file.workspaceId || workspaceId,
name: file.originalName,
key: file.key,
path: `${pathPrefix}${encodeURIComponent(file.key)}?context=workspace`,
size: file.size,
type: file.contentType,
uploadedBy: file.userId,
deletedAt: file.deletedAt,
uploadedAt: file.uploadedAt,
}
Comment thread
TheodoreSpeaks marked this conversation as resolved.
}

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/tools/file/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const fileWriteTool: ToolConfig<FileWriteParams, ToolResponse> = {
},

request: {
url: '/api/tools/sim-file/manage',
url: '/api/tools/file/manage',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({
Expand Down