(chatIds: string[])
| 34 | * 2. fileAttachments[].key inside each copilot_messages.content |
| 35 | */ |
| 36 | export async function collectChatFiles(chatIds: string[]): Promise<FileRef[]> { |
| 37 | const files: FileRef[] = [] |
| 38 | if (chatIds.length === 0) return files |
| 39 | |
| 40 | const seen = new Set<string>() |
| 41 | |
| 42 | for (const chunk of chunkArray(chatIds, CHAT_FILE_COLLECT_CHUNK_SIZE)) { |
| 43 | const [linkedFiles, messageRows] = await Promise.all([ |
| 44 | db |
| 45 | .select({ key: workspaceFiles.key, context: workspaceFiles.context }) |
| 46 | .from(workspaceFiles) |
| 47 | .where( |
| 48 | and( |
| 49 | inArray(workspaceFiles.chatId, chunk), |
| 50 | isNull(workspaceFiles.deletedAt), |
| 51 | inArray(workspaceFiles.context, [...CHAT_SCOPED_CONTEXTS]) |
| 52 | ) |
| 53 | ), |
| 54 | // Scan every message row for the chat (no deleted_at filter): this is a |
| 55 | // deletion path collecting blob keys, so attachments on any row count. |
| 56 | db |
| 57 | .select({ content: copilotMessages.content }) |
| 58 | .from(copilotMessages) |
| 59 | .where(inArray(copilotMessages.chatId, chunk)), |
| 60 | ]) |
| 61 | |
| 62 | for (const f of linkedFiles) { |
| 63 | if (!seen.has(f.key)) { |
| 64 | seen.add(f.key) |
| 65 | files.push({ key: f.key, context: f.context as ChatScopedContext }) |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | for (const row of messageRows) { |
| 70 | const msg = row.content |
| 71 | if (!msg || typeof msg !== 'object') continue |
| 72 | const attachments = (msg as Record<string, unknown>).fileAttachments |
| 73 | if (!Array.isArray(attachments)) continue |
| 74 | for (const attachment of attachments) { |
| 75 | if ( |
| 76 | attachment && |
| 77 | typeof attachment === 'object' && |
| 78 | (attachment as Record<string, unknown>).key |
| 79 | ) { |
| 80 | const key = (attachment as Record<string, unknown>).key as string |
| 81 | if (!seen.has(key)) { |
| 82 | seen.add(key) |
| 83 | files.push({ key, context: 'copilot' }) |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | return files |
| 91 | } |
| 92 | |
| 93 | /** Groups files by storage context so each context can use one batch DELETE call. */ |
no test coverage detected