( chatId: string, newResources: ChatResource[] )
| 25 | * Updates the title of existing resources if the new title is more specific. |
| 26 | */ |
| 27 | export async function persistChatResources( |
| 28 | chatId: string, |
| 29 | newResources: ChatResource[] |
| 30 | ): Promise<void> { |
| 31 | const toMerge = newResources.filter((r) => r.id !== 'streaming-file') |
| 32 | if (toMerge.length === 0) return |
| 33 | |
| 34 | try { |
| 35 | const [chat] = await db |
| 36 | .select({ resources: copilotChats.resources }) |
| 37 | .from(copilotChats) |
| 38 | .where(eq(copilotChats.id, chatId)) |
| 39 | .limit(1) |
| 40 | |
| 41 | if (!chat) return |
| 42 | |
| 43 | const existing = Array.isArray(chat.resources) ? (chat.resources as ChatResource[]) : [] |
| 44 | const map = new Map<string, ChatResource>() |
| 45 | |
| 46 | for (const r of existing) { |
| 47 | map.set(`${r.type}:${r.id}`, r) |
| 48 | } |
| 49 | |
| 50 | for (const r of toMerge) { |
| 51 | const key = `${r.type}:${r.id}` |
| 52 | const prev = map.get(key) |
| 53 | if ( |
| 54 | !prev || |
| 55 | (GENERIC_RESOURCE_TITLES.has(prev.title) && !GENERIC_RESOURCE_TITLES.has(r.title)) |
| 56 | ) { |
| 57 | map.set(key, r) |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | const merged = Array.from(map.values()) |
| 62 | |
| 63 | await db |
| 64 | .update(copilotChats) |
| 65 | .set({ resources: sql`${JSON.stringify(merged)}::jsonb` }) |
| 66 | .where(eq(copilotChats.id, chatId)) |
| 67 | } catch (err) { |
| 68 | logger.warn('Failed to persist chat resources', { |
| 69 | chatId, |
| 70 | error: toError(err).message, |
| 71 | }) |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Removes resources from a chat's JSONB resources column by type+id. |
no test coverage detected