import { eq, and, desc, lt, sql, type Db, agentLogs } from '@mosaicstack/db'; export type AgentLog = typeof agentLogs.$inferSelect; export type NewAgentLog = typeof agentLogs.$inferInsert; export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; export type LogCategory = 'decision' | 'tool_use' | 'learning' | 'error' | 'general'; export type LogTier = 'hot' | 'warm' | 'cold'; export interface LogQuery { userId?: string; sessionId?: string; level?: LogLevel; category?: LogCategory; tier?: LogTier; since?: Date; until?: Date; limit?: number; offset?: number; } export function createAgentLogsRepo(db: Db) { return { async ingest(entry: NewAgentLog): Promise { const rows = await db.insert(agentLogs).values(entry).returning(); return rows[0]!; }, async ingestBatch(entries: NewAgentLog[]): Promise { if (entries.length === 0) return []; return db.insert(agentLogs).values(entries).returning(); }, async query(params: LogQuery): Promise { const conditions = []; if (params.userId) conditions.push(eq(agentLogs.userId, params.userId)); if (params.sessionId) conditions.push(eq(agentLogs.sessionId, params.sessionId)); if (params.level) conditions.push(eq(agentLogs.level, params.level)); if (params.category) conditions.push(eq(agentLogs.category, params.category)); if (params.tier) conditions.push(eq(agentLogs.tier, params.tier)); if (params.since) conditions.push(sql`${agentLogs.createdAt} >= ${params.since}`); if (params.until) conditions.push(sql`${agentLogs.createdAt} <= ${params.until}`); const where = conditions.length > 0 ? and(...conditions) : undefined; return db .select() .from(agentLogs) .where(where) .orderBy(desc(agentLogs.createdAt)) .limit(params.limit ?? 100) .offset(params.offset ?? 0); }, async findById(id: string): Promise { const rows = await db.select().from(agentLogs).where(eq(agentLogs.id, id)); return rows[0]; }, /** * Transition hot logs older than the cutoff to warm tier. * Returns the number of logs transitioned. */ async promoteToWarm(olderThan: Date): Promise { const result = await db .update(agentLogs) .set({ tier: 'warm', summarizedAt: new Date() }) .where(and(eq(agentLogs.tier, 'hot'), lt(agentLogs.createdAt, olderThan))) .returning(); return result.length; }, /** * Transition warm logs older than the cutoff to cold tier. */ async promoteToCold(olderThan: Date): Promise { const result = await db .update(agentLogs) .set({ tier: 'cold', archivedAt: new Date() }) .where(and(eq(agentLogs.tier, 'warm'), lt(agentLogs.createdAt, olderThan))) .returning(); return result.length; }, /** * Delete cold logs older than the retention period. */ async purge(olderThan: Date): Promise { const result = await db .delete(agentLogs) .where(and(eq(agentLogs.tier, 'cold'), lt(agentLogs.createdAt, olderThan))) .returning(); return result.length; }, /** * Get hot logs ready for summarization (decisions + learnings). */ async getLogsForSummarization(olderThan: Date, limit = 100): Promise { return db .select() .from(agentLogs) .where( and( eq(agentLogs.tier, 'hot'), lt(agentLogs.createdAt, olderThan), sql`${agentLogs.category} IN ('decision', 'learning', 'tool_use')`, ), ) .orderBy(agentLogs.createdAt) .limit(limit); }, }; } export type AgentLogsRepo = ReturnType;