workspaceKnowledgeRoutes.ts 4.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import Router from '@koa/router'
  2. import { z } from 'zod'
  3. import type { LocalAuthStore } from './localAuth.js'
  4. import type { WorkspaceRuntime } from './workspaceRuntime.js'
  5. import { parse } from './workspaceStore.js'
  6. import { memoryForgetInput, memoryQueryInput, memoryScopeInput, memoryWriteInput, projectSaveInput } from './workspaceKnowledgeTools.js'
  7. import { HttpError } from './errors.js'
  8. export function workspaceKnowledgeRouter(runtime: WorkspaceRuntime, auth: LocalAuthStore): Router {
  9. const router = new Router({ prefix: '/api/app/workspace' }), knowledge = runtime.knowledge
  10. router.use(async (ctx, next) => { ctx.set('Cache-Control', 'no-store'); await next() })
  11. router.get('/projects', ctx => { ctx.body = { projects: knowledge.projects(auth.require(ctx).id) } })
  12. router.post('/projects', ctx => {
  13. const owner = auth.require(ctx).id, input = parse(projectSaveInput, ctx.request.body)
  14. for (const id of input.memberIds) runtime.nodes.requireSource(owner, knowledge.agent(owner, id))
  15. ctx.body = { project: knowledge.saveProject(owner, input) }
  16. })
  17. router.get('/memories', async ctx => {
  18. const owner = auth.require(ctx).id, query = parse(memoryQueryInput, ctx.query)
  19. const memories = (await knowledge.memories(owner, query)).filter(memory => { try { runtime.nodes.requireSource(owner, knowledge.agent(owner, memory.agentId)); return true } catch { return false } })
  20. ctx.set('Cache-Control', 'no-store'); ctx.body = { memories }
  21. })
  22. router.post('/memories', async ctx => {
  23. const owner = auth.require(ctx).id, input = parse(memoryWriteInput, ctx.request.body)
  24. runtime.nodes.requireSource(owner, knowledge.agent(owner, input.agentId))
  25. ctx.body = { memory: await knowledge.writeMemory(owner, input) }
  26. })
  27. router.post('/memories/forget', async ctx => {
  28. const owner = auth.require(ctx).id, input = parse(memoryForgetInput, ctx.request.body)
  29. runtime.nodes.requireSource(owner, knowledge.agent(owner, input.agentId))
  30. ctx.body = await knowledge.forget(owner, input)
  31. })
  32. router.get('/memories/:id/revisions', async ctx => {
  33. const owner = auth.require(ctx).id, scope = parse(memoryScopeInput, ctx.query)
  34. runtime.nodes.requireSource(owner, knowledge.agent(owner, scope.agentId))
  35. ctx.body = { revisions: await knowledge.revisions(owner, scope, ctx.params.id) }
  36. })
  37. router.get('/memory-export', async ctx => {
  38. const owner = auth.require(ctx).id, query = parse(memoryQueryInput, ctx.query), memories = await knowledge.memories(owner, query)
  39. for (const memory of memories) runtime.nodes.requireSource(owner, knowledge.agent(owner, memory.agentId))
  40. ctx.set('Content-Disposition', 'attachment; filename="bot-memory.md"')
  41. ctx.type = 'text/markdown; charset=utf-8'
  42. ctx.body = `# Bot 模式记忆\n\n${memories.map(m => `- (${new Date(m.createdAt).toISOString().slice(0, 10)}) ${m.content}`).join('\n')}\n`
  43. })
  44. router.get('/memory-jobs', ctx => {
  45. const owner = auth.require(ctx).id, input = parse(z.object({ agentId: z.string().uuid().optional() }).strict(), ctx.query)
  46. if (input.agentId) runtime.nodes.requireSource(owner, knowledge.agent(owner, input.agentId))
  47. ctx.body = { jobs: knowledge.jobs(owner, input.agentId).slice(0, 200) }
  48. })
  49. router.post('/memory-jobs/:id/retry', ctx => {
  50. const owner = auth.require(ctx).id, job = knowledge.jobs(owner).find(j => j.id === ctx.params.id)
  51. if (!job) throw new HttpError(404, '提炼记录不存在', 'memory_job_not_found')
  52. runtime.nodes.requireSource(owner, knowledge.agent(owner, job.agentId))
  53. if (job.status === 'failed') knowledge.saveJob(owner, { ...job, status: 'pending', attempts: 0, nextAt: Date.now(), error: undefined })
  54. ctx.body = { ok: true }
  55. })
  56. router.get('/collaboration', ctx => {
  57. const owner = auth.require(ctx).id, query = parse(z.object({ conversationId: z.string().uuid().optional() }).strict(), ctx.query)
  58. if (query.conversationId) runtime.store.require(owner, 'conversation', query.conversationId)
  59. ctx.body = { messages: runtime.collaboration.list(owner, query.conversationId) }
  60. })
  61. router.post('/collaboration/:id/stop', async ctx => {
  62. const owner = auth.require(ctx).id, peer = runtime.store.require<import('../shared/workspaceKnowledge.js').WorkspacePeerMessage>(owner, 'peer-message', ctx.params.id)
  63. await runtime.collaboration.stopFor(owner, chain => chain.id === peer.chainId)
  64. ctx.body = { ok: true }
  65. })
  66. return router
  67. }