(params: {
skills: Array<{
id?: string
name: string
description: string
content: string
}>
workspaceId: string
userId: string
requestId?: string
})
| 123 | * Can be called from API routes or internal services. |
| 124 | */ |
| 125 | export async function upsertSkills(params: { |
| 126 | skills: Array<{ |
| 127 | id?: string |
| 128 | name: string |
| 129 | description: string |
| 130 | content: string |
| 131 | }> |
| 132 | workspaceId: string |
| 133 | userId: string |
| 134 | requestId?: string |
| 135 | }): Promise<UpsertSkillsResult> { |
| 136 | const { skills, workspaceId, userId, requestId = generateRequestId() } = params |
| 137 | |
| 138 | // Built-in template skills are read-only and must never be written to the DB. |
| 139 | if (skills.some((s) => s.id && isBuiltinSkillId(s.id))) { |
| 140 | throw new Error('Built-in skills are read-only and cannot be modified') |
| 141 | } |
| 142 | |
| 143 | return await db.transaction(async (tx) => { |
| 144 | const touched: TouchedSkill[] = [] |
| 145 | |
| 146 | for (const s of skills) { |
| 147 | const nowTime = new Date() |
| 148 | |
| 149 | if (s.id) { |
| 150 | const existingSkill = await tx |
| 151 | .select() |
| 152 | .from(skill) |
| 153 | .where(and(eq(skill.id, s.id), eq(skill.workspaceId, workspaceId))) |
| 154 | .limit(1) |
| 155 | |
| 156 | if (existingSkill.length > 0) { |
| 157 | if (s.name !== existingSkill[0].name) { |
| 158 | const nameConflict = await tx |
| 159 | .select({ id: skill.id }) |
| 160 | .from(skill) |
| 161 | .where( |
| 162 | and(eq(skill.workspaceId, workspaceId), eq(skill.name, s.name), ne(skill.id, s.id)) |
| 163 | ) |
| 164 | .limit(1) |
| 165 | |
| 166 | if (nameConflict.length > 0) { |
| 167 | throw new Error(`A skill with the name "${s.name}" already exists in this workspace`) |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | await tx |
| 172 | .update(skill) |
| 173 | .set({ |
| 174 | name: s.name, |
| 175 | description: s.description, |
| 176 | content: s.content, |
| 177 | updatedAt: nowTime, |
| 178 | }) |
| 179 | .where(and(eq(skill.id, s.id), eq(skill.workspaceId, workspaceId))) |
| 180 | |
| 181 | touched.push({ id: s.id, name: s.name, operation: 'updated' }) |
| 182 | logger.info(`[${requestId}] Updated skill ${s.id}`) |
no test coverage detected