import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common"; import type { EmotionTag } from "@kokorone/shared"; import { WorkingMemoryService } from "./working-memory.service.js"; import { MemoryConsolidationService } from "./memory-consolidation.service.js"; import { ForgettingSweepService } from "./forgetting-sweep.service.js"; import { KeywordMemoryRetriever } from "./memory-retriever.service.js"; import { InProcessJobQueue } from "./in-process-job-queue.service.js"; interface AppendMessageBody { role: "user" | "character"; content: string; emotionTag?: EmotionTag; emotionIntensity?: number; } @Controller("memory") export class MemoryController { // C-3 排程抽象層驗證用:記錄測試性延遲工作是否已觸發,僅供 smoke test 檢查 schedule(at) 準時性。 private readonly scheduledJobFired = new Map(); constructor( private readonly workingMemory: WorkingMemoryService, private readonly consolidation: MemoryConsolidationService, private readonly forgettingSweep: ForgettingSweepService, private readonly retriever: KeywordMemoryRetriever, private readonly jobQueue: InProcessJobQueue, ) {} @Post(":characterId/sessions/:sessionId/messages") appendMessage(@Param("sessionId") sessionId: string, @Body() body: AppendMessageBody) { this.workingMemory.append(sessionId, { role: body.role, content: body.content, timestamp: new Date(), emotionTag: body.emotionTag, emotionIntensity: body.emotionIntensity, }); return { context: this.workingMemory.getContext(sessionId) }; } @Get(":characterId/sessions/:sessionId/messages") getContext(@Param("sessionId") sessionId: string) { return { context: this.workingMemory.getContext(sessionId) }; } @Post(":characterId/sessions/:sessionId/consolidate") async consolidate(@Param("characterId") characterId: string, @Param("sessionId") sessionId: string) { await this.consolidation.consolidate(characterId, sessionId); return { ok: true }; } @Get(":characterId/retrieve") async retrieve( @Param("characterId") characterId: string, @Query("query") query: string, @Query("limit") limit?: string, ) { const results = await this.retriever.retrieve(characterId, query ?? "", limit ? Number(limit) : undefined); return { results }; } @Post("forgetting-sweep") async runForgettingSweep(@Body() body: { now?: string }) { const now = body?.now ? new Date(body.now) : new Date(); return this.forgettingSweep.run(now); } @Post("test-scheduled-job") scheduleTestJob(@Body() body: { id: string; delaySeconds: number }) { this.scheduledJobFired.set(body.id, false); const at = new Date(Date.now() + body.delaySeconds * 1000); this.jobQueue.schedule(at, `test-job-${body.id}`, () => { this.scheduledJobFired.set(body.id, true); }); return { scheduled: true }; } @Get("test-scheduled-job/:id") checkTestJob(@Param("id") id: string) { return { fired: this.scheduledJobFired.get(id) ?? false }; } }