( files: FileRef[], label: string )
| 92 | |
| 93 | /** Groups files by storage context so each context can use one batch DELETE call. */ |
| 94 | export async function deleteStorageFiles( |
| 95 | files: FileRef[], |
| 96 | label: string |
| 97 | ): Promise<{ filesDeleted: number; filesFailed: number }> { |
| 98 | const stats = { filesDeleted: 0, filesFailed: 0 } |
| 99 | if (files.length === 0 || !isUsingCloudStorage()) return stats |
| 100 | |
| 101 | const keysByContext = new Map<ChatScopedContext, string[]>() |
| 102 | for (const file of files) { |
| 103 | const bucket = keysByContext.get(file.context) |
| 104 | if (bucket) bucket.push(file.key) |
| 105 | else keysByContext.set(file.context, [file.key]) |
| 106 | } |
| 107 | |
| 108 | for (const [context, keys] of keysByContext) { |
| 109 | const result = await StorageService.deleteFiles(keys, context) |
| 110 | stats.filesDeleted += result.deleted |
| 111 | stats.filesFailed += result.failed.length |
| 112 | for (const { key, error } of result.failed) { |
| 113 | logger.error(`[${label}] Failed to delete storage file ${key} (context: ${context}):`, { |
| 114 | error, |
| 115 | }) |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | return stats |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * Call the copilot backend to delete chat data (memory_files, checkpoints, task_chains, etc.) |
no test coverage detected