fiemwl / src /app /api /memory /route.ts
automindy's picture
Upload 1980 files
6111b2b verified
Raw
History Blame Contribute Delete
4.15 kB
import { NextResponse } from "next/server";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { listMemories, createMemory, getMemoryTokensUsed } from "@/lib/memory/store";
import { memoryCache } from "@/lib/memory/cache";
import { MemoryType } from "@/lib/memory/types";
import { parsePaginationParams, buildPaginatedResponse } from "@/shared/types/pagination";
import { z } from "zod";
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
const createMemorySchema = z.object({
content: z.string().min(1),
key: z.string().min(1),
type: z.nativeEnum(MemoryType).default(MemoryType.FACTUAL),
sessionId: z.string().default(""),
apiKeyId: z.string().default(""),
metadata: z.record(z.string(), z.unknown()).default({}),
expiresAt: z.coerce.date().nullable().default(null),
});
export async function GET(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
try {
const url = new URL(request.url);
const { searchParams } = url;
const paginationParams = parsePaginationParams(searchParams);
const rawOffset = searchParams.get("offset");
const offset =
typeof rawOffset === "string" && rawOffset.trim().length > 0
? Math.max(0, Number.parseInt(rawOffset, 10) || 0)
: undefined;
const query = searchParams.get("q") || undefined;
const apiKeyId = searchParams.get("apiKeyId") || undefined;
const type = (searchParams.get("type") as any) || undefined;
const sessionId = searchParams.get("sessionId") || undefined;
const result = await listMemories({
apiKeyId,
type,
sessionId,
query,
limit: paginationParams.limit,
offset,
page: offset === undefined ? paginationParams.page : undefined,
});
// Total tokens across all memories (computed in SQL inside the domain module
// to avoid loading every memory's content into process memory).
const tokensUsed = getMemoryTokensUsed(apiKeyId);
// Compute hit rate from memory cache
const cacheStats = memoryCache.stats();
const totalCacheRequests = cacheStats.hits + cacheStats.misses;
const hitRate = totalCacheRequests > 0 ? cacheStats.hits / totalCacheRequests : 0;
const stats = {
total: result.total,
byType: result.byType ?? {},
tokensUsed,
hitRate,
// Plan 21 / D18: MemoriesTab uses cacheStats to decide whether to render
// the Hit Rate card (only when hits + misses > 0). Without this field
// the card would never appear even when hitRate > 0.
cacheStats: { hits: cacheStats.hits, misses: cacheStats.misses },
};
const responsePagination =
offset === undefined
? paginationParams
: {
...paginationParams,
page: Math.floor(offset / paginationParams.limit) + 1,
};
const paginatedResponse = buildPaginatedResponse(result.data, result.total, responsePagination);
return NextResponse.json({
...paginatedResponse,
stats,
});
} catch (err: unknown) {
const message = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
return NextResponse.json({ error: { message } }, { status: 500 });
}
}
export async function POST(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
try {
const rawBody = await request.json();
const validation = validateBody(createMemorySchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json(validation.error, { status: 400 });
}
const memoryId = await createMemory(validation.data);
return NextResponse.json({ success: true, id: memoryId });
} catch (err: unknown) {
const message = sanitizeErrorMessage(err instanceof Error ? err.message : String(err));
return NextResponse.json({ error: { message } }, { status: 400 });
}
}