d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
249 lines
6.2 KiB
TypeScript
249 lines
6.2 KiB
TypeScript
import { createLogger } from '@sim/logger'
|
|
import { getBaseUrl } from '@/lib/core/utils/urls'
|
|
import {
|
|
deleteFile,
|
|
downloadFile,
|
|
generatePresignedDownloadUrl,
|
|
generatePresignedUploadUrl,
|
|
uploadFile,
|
|
} from '@/lib/uploads/core/storage-service'
|
|
import type { PresignedUrlResponse } from '@/lib/uploads/shared/types'
|
|
import { isImageFileType } from '@/lib/uploads/utils/file-utils'
|
|
|
|
const logger = createLogger('CopilotFileManager')
|
|
|
|
const SUPPORTED_FILE_TYPES = [
|
|
// Images
|
|
'image/jpeg',
|
|
'image/jpg',
|
|
'image/png',
|
|
'image/gif',
|
|
'image/webp',
|
|
'image/svg+xml',
|
|
// Documents
|
|
'application/pdf',
|
|
'text/plain',
|
|
'text/csv',
|
|
'text/markdown',
|
|
'text/html',
|
|
'application/json',
|
|
'application/xml',
|
|
'text/xml',
|
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
'text/x-pptxgenjs',
|
|
'text/x-docxjs',
|
|
'text/x-python-pdf',
|
|
'text/x-python-xlsx',
|
|
]
|
|
|
|
/**
|
|
* Check if a MIME type is supported for copilot attachments
|
|
*/
|
|
export function isSupportedFileType(mimeType: string): boolean {
|
|
return SUPPORTED_FILE_TYPES.includes(mimeType.toLowerCase())
|
|
}
|
|
|
|
interface CopilotFileAttachment {
|
|
key: string
|
|
filename: string
|
|
media_type: string
|
|
}
|
|
|
|
export interface GenerateCopilotUploadUrlOptions {
|
|
fileName: string
|
|
contentType: string
|
|
fileSize: number
|
|
userId: string
|
|
expirationSeconds?: number
|
|
}
|
|
|
|
export interface CopilotStoredFile {
|
|
id: string
|
|
key: string
|
|
context: 'copilot'
|
|
name: string
|
|
url: string
|
|
size: number
|
|
type: string
|
|
mimeType: string
|
|
}
|
|
|
|
/**
|
|
* Generate a presigned URL for copilot file upload
|
|
*
|
|
* Images and document files are allowed for copilot uploads.
|
|
* Requires authenticated user session.
|
|
*
|
|
* @param options Upload URL generation options
|
|
* @returns Presigned URL response with upload URL and file key
|
|
* @throws Error if file type is unsupported or user is not authenticated
|
|
*/
|
|
export async function generateCopilotUploadUrl(
|
|
options: GenerateCopilotUploadUrlOptions
|
|
): Promise<PresignedUrlResponse> {
|
|
const { fileName, contentType, fileSize, userId, expirationSeconds = 3600 } = options
|
|
|
|
if (!userId?.trim()) {
|
|
throw new Error('Authenticated user session is required for copilot uploads')
|
|
}
|
|
|
|
if (!isSupportedFileType(contentType) && !isImageFileType(contentType)) {
|
|
throw new Error(
|
|
'Unsupported file type. Allowed: images (JPEG, PNG, GIF, WebP), PDF, and text files (TXT, CSV, MD, HTML, JSON, XML).'
|
|
)
|
|
}
|
|
|
|
const presignedUrlResponse = await generatePresignedUploadUrl({
|
|
fileName,
|
|
contentType,
|
|
fileSize,
|
|
context: 'copilot',
|
|
userId,
|
|
expirationSeconds,
|
|
})
|
|
|
|
logger.info(`Generated copilot upload URL for: ${fileName}`, {
|
|
key: presignedUrlResponse.key,
|
|
userId,
|
|
})
|
|
|
|
return presignedUrlResponse
|
|
}
|
|
|
|
export async function uploadCopilotFile(options: {
|
|
buffer: Buffer
|
|
fileName: string
|
|
contentType: string
|
|
userId: string
|
|
}): Promise<CopilotStoredFile> {
|
|
const fileInfo = await uploadFile({
|
|
file: options.buffer,
|
|
fileName: options.fileName,
|
|
contentType: options.contentType,
|
|
context: 'copilot',
|
|
metadata: {
|
|
userId: options.userId,
|
|
originalName: options.fileName,
|
|
uploadedAt: new Date().toISOString(),
|
|
purpose: 'copilot-tool-output',
|
|
},
|
|
})
|
|
|
|
const url = `${getBaseUrl()}${fileInfo.path}`
|
|
|
|
logger.info(`Stored copilot tool output: ${options.fileName}`, {
|
|
key: fileInfo.key,
|
|
size: fileInfo.size,
|
|
userId: options.userId,
|
|
})
|
|
|
|
return {
|
|
id: fileInfo.key,
|
|
key: fileInfo.key,
|
|
context: 'copilot',
|
|
name: fileInfo.name,
|
|
url,
|
|
size: fileInfo.size,
|
|
type: fileInfo.type,
|
|
mimeType: fileInfo.type,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download a copilot file from storage
|
|
*
|
|
* Uses the unified storage service with explicit copilot context.
|
|
* Handles S3, Azure Blob, and local storage automatically.
|
|
*
|
|
* @param key File storage key
|
|
* @returns File buffer
|
|
* @throws Error if file not found or download fails
|
|
*/
|
|
export async function downloadCopilotFile(key: string): Promise<Buffer> {
|
|
try {
|
|
const fileBuffer = await downloadFile({
|
|
key,
|
|
context: 'copilot',
|
|
})
|
|
|
|
logger.info(`Successfully downloaded copilot file: ${key}`, {
|
|
size: fileBuffer.length,
|
|
})
|
|
|
|
return fileBuffer
|
|
} catch (error) {
|
|
logger.error(`Failed to download copilot file: ${key}`, error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process copilot file attachments for chat messages
|
|
*
|
|
* Downloads files from storage and validates they are supported types.
|
|
* Skips unsupported files with a warning.
|
|
*
|
|
* @param attachments Array of file attachments
|
|
* @param requestId Request identifier for logging
|
|
* @returns Array of buffers for successfully downloaded files
|
|
*/
|
|
export async function processCopilotAttachments(
|
|
attachments: CopilotFileAttachment[],
|
|
requestId: string
|
|
): Promise<Array<{ buffer: Buffer; attachment: CopilotFileAttachment }>> {
|
|
const results: Array<{ buffer: Buffer; attachment: CopilotFileAttachment }> = []
|
|
|
|
for (const attachment of attachments) {
|
|
try {
|
|
if (!isSupportedFileType(attachment.media_type)) {
|
|
logger.warn(`[${requestId}] Unsupported file type: ${attachment.media_type}`)
|
|
continue
|
|
}
|
|
|
|
const buffer = await downloadCopilotFile(attachment.key)
|
|
|
|
results.push({ buffer, attachment })
|
|
} catch (error) {
|
|
logger.error(`[${requestId}] Failed to process file ${attachment.filename}:`, error)
|
|
}
|
|
}
|
|
|
|
logger.info(`Successfully processed ${results.length}/${attachments.length} attachments`, {
|
|
requestId,
|
|
})
|
|
|
|
return results
|
|
}
|
|
|
|
/**
|
|
* Generate a presigned download URL for a copilot file
|
|
*
|
|
* @param key File storage key
|
|
* @param expirationSeconds Time in seconds until URL expires (default: 1 hour)
|
|
* @returns Presigned download URL
|
|
*/
|
|
export async function generateCopilotDownloadUrl(
|
|
key: string,
|
|
expirationSeconds = 3600
|
|
): Promise<string> {
|
|
const downloadUrl = await generatePresignedDownloadUrl(key, 'copilot', expirationSeconds)
|
|
|
|
logger.info(`Generated copilot download URL for: ${key}`)
|
|
|
|
return downloadUrl
|
|
}
|
|
|
|
/**
|
|
* Delete a copilot file from storage
|
|
*
|
|
* @param key File storage key
|
|
*/
|
|
export async function deleteCopilotFile(key: string): Promise<void> {
|
|
await deleteFile({
|
|
key,
|
|
context: 'copilot',
|
|
})
|
|
|
|
logger.info(`Successfully deleted copilot file: ${key}`)
|
|
}
|