Files
Kokorone/apps/api/src/memory/memory.controller.ts
T

89 lines
3.2 KiB
TypeScript

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<string, boolean>();
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")
async appendMessage(@Param("sessionId") sessionId: string, @Body() body: AppendMessageBody) {
await this.workingMemory.append(sessionId, {
role: body.role,
content: body.content,
timestamp: new Date(),
emotionTag: body.emotionTag,
emotionIntensity: body.emotionIntensity,
});
return { context: await this.workingMemory.getContext(sessionId) };
}
@Get(":characterId/sessions/:sessionId/messages")
async getContext(@Param("sessionId") sessionId: string) {
return { context: await 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,
@Query("relatedUserId") relatedUserId?: string,
@Query("excludePrivate") excludePrivate?: string,
) {
const results = await this.retriever.retrieve(characterId, query ?? "", {
limit: limit ? Number(limit) : undefined,
relatedUserId,
excludePrivate: excludePrivate === "true",
});
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 };
}
}