feat(sync): 添加 Vault 所有的用户 Skill 记录
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import { beforeEach, expect, it, vi } from 'vitest'
|
||||
import { apiClient } from './apiClient'
|
||||
import * as service from './skillService'
|
||||
|
||||
vi.mock('./apiClient', () => {
|
||||
const client = { get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn(), postBinary: vi.fn() }
|
||||
return { apiClient: client, default: client }
|
||||
})
|
||||
|
||||
const request = {
|
||||
revision: '', name: 'Review', description: '', prompt: 'Review carefully', tools: ['notes.read'],
|
||||
permissions: ['notes.read'], retrieval: { top_k: 10, rerank: true, citation: true }, required_capabilities: ['chat'],
|
||||
}
|
||||
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
|
||||
it('uses dedicated user Skill routes and stable idempotency keys', async () => {
|
||||
const createOperation = '00000000-0000-4000-8000-000000000001'
|
||||
const updateOperation = '00000000-0000-4000-8000-000000000002'
|
||||
const deleteOperation = '00000000-0000-4000-8000-000000000003'
|
||||
vi.mocked(apiClient.get).mockResolvedValue({ items: [] })
|
||||
vi.mocked(apiClient.post).mockResolvedValue({})
|
||||
vi.mocked(apiClient.put).mockResolvedValue({})
|
||||
vi.mocked(apiClient.delete).mockResolvedValue({ status: 'completed' })
|
||||
await service.listUserSkills()
|
||||
await service.createUserSkill(request, createOperation)
|
||||
await service.updateUserSkill('user_skill_' + '1'.repeat(32), { ...request, revision: 'a'.repeat(64) }, updateOperation)
|
||||
await service.deleteUserSkill('user_skill_' + '1'.repeat(32), 'b'.repeat(64), deleteOperation)
|
||||
expect(apiClient.get).toHaveBeenCalledWith('/api/user-skills', { params: { limit: 100, offset: 0 } })
|
||||
expect(apiClient.post).toHaveBeenCalledWith('/api/user-skills', request, { headers: { 'Idempotency-Key': createOperation } })
|
||||
expect(apiClient.put).toHaveBeenCalledWith('/api/user-skills/user_skill_' + '1'.repeat(32), expect.objectContaining({ revision: 'a'.repeat(64) }), { headers: { 'Idempotency-Key': updateOperation } })
|
||||
expect(apiClient.delete).toHaveBeenCalledWith('/api/user-skills/user_skill_' + '1'.repeat(32), { params: { revision: 'b'.repeat(64) }, headers: { 'Idempotency-Key': deleteOperation } })
|
||||
})
|
||||
|
||||
it('loads every bounded Host page instead of silently truncating user Skills', async () => {
|
||||
const items = Array.from({ length: 101 }, (_, index) => ({ skill_id: `user_skill_${String(index).padStart(32, '0')}` }))
|
||||
vi.mocked(apiClient.get)
|
||||
.mockResolvedValueOnce({ items: items.slice(0, 100), page: { total: 101 } })
|
||||
.mockResolvedValueOnce({ items: items.slice(100), page: { total: 101 } })
|
||||
expect(await service.listUserSkills()).toHaveLength(101)
|
||||
expect(apiClient.get).toHaveBeenNthCalledWith(2, '/api/user-skills', { params: { limit: 100, offset: 100 } })
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import apiClient from './apiClient'
|
||||
import type { ApiSkill, OperationResponse, Skill } from '@/contracts'
|
||||
import type { ApiSkill, OperationResponse, Skill, UserSkill, UserSkillWriteRequest } from '@/contracts'
|
||||
|
||||
function toSkill(skill: ApiSkill): Skill {
|
||||
const { manifest } = skill
|
||||
@@ -45,3 +45,27 @@ export async function disableSkill(skillId: string): Promise<Skill> {
|
||||
export async function uninstallSkill(skillId: string): Promise<OperationResponse> {
|
||||
return apiClient.delete(`/api/skills/${skillId}`)
|
||||
}
|
||||
|
||||
export async function listUserSkills(): Promise<UserSkill[]> {
|
||||
const items: UserSkill[] = []
|
||||
for (let page = 0; page < 100; page += 1) {
|
||||
const response = await apiClient.get<{ items: UserSkill[]; page?: { total: number } }>('/api/user-skills', { params: { limit: 100, offset: items.length } })
|
||||
items.push(...response.items)
|
||||
if (!response.items.length || items.length >= (response.page?.total ?? items.length)) return items
|
||||
}
|
||||
throw new Error('USER_SKILL_LIST_LIMIT_EXCEEDED')
|
||||
}
|
||||
|
||||
export async function createUserSkill(request: UserSkillWriteRequest, operationId: string = crypto.randomUUID()): Promise<UserSkill> {
|
||||
return apiClient.post('/api/user-skills', request, { headers: { 'Idempotency-Key': operationId } })
|
||||
}
|
||||
|
||||
export async function updateUserSkill(skillId: string, request: UserSkillWriteRequest, operationId: string = crypto.randomUUID()): Promise<UserSkill> {
|
||||
return apiClient.put(`/api/user-skills/${skillId}`, request, { headers: { 'Idempotency-Key': operationId } })
|
||||
}
|
||||
|
||||
export async function deleteUserSkill(skillId: string, revision: string, operationId: string = crypto.randomUUID()): Promise<OperationResponse> {
|
||||
return apiClient.delete(`/api/user-skills/${skillId}`, {
|
||||
params: { revision }, headers: { 'Idempotency-Key': operationId },
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user