import { ConvexError, v } from 'convex/values'; import type { Doc, Id } from './_generated/dataModel'; import { internalMutation, query } from './_generated/server'; import { getOwnedSpoon, getRequiredUserId } from './model'; const reviewStatus = v.union( v.literal('queued'), v.literal('running'), v.literal('completed'), v.literal('failed'), ); const reviewType = v.union( v.literal('upstream_update'), v.literal('manual_prompt'), v.literal('merge_safety'), ); const risk = v.union( v.literal('unknown'), v.literal('low'), v.literal('medium'), v.literal('high'), ); const recommendedAction = v.union( v.literal('sync'), v.literal('open_review_pr'), v.literal('manual_review'), v.literal('do_not_sync'), v.literal('unknown'), ); export const listForSpoon = query({ args: { spoonId: v.id('spoons'), limit: v.optional(v.number()) }, handler: async (ctx, { spoonId, limit }) => { const ownerId = await getRequiredUserId(ctx); await getOwnedSpoon(ctx, spoonId, ownerId); return await ctx.db .query('aiReviews') .withIndex('by_spoon', (q) => q.eq('spoonId', spoonId)) .order('desc') .take(limit ?? 25); }, }); export const getLatestForSpoon = query({ args: { spoonId: v.id('spoons') }, handler: async (ctx, { spoonId }) => { const ownerId = await getRequiredUserId(ctx); await getOwnedSpoon(ctx, spoonId, ownerId); return await ctx.db .query('aiReviews') .withIndex('by_spoon', (q) => q.eq('spoonId', spoonId)) .order('desc') .first(); }, }); export const listRecent = query({ args: { limit: v.optional(v.number()) }, handler: async (ctx, { limit }) => { const ownerId = await getRequiredUserId(ctx); return await ctx.db .query('aiReviews') .withIndex('by_owner', (q) => q.eq('ownerId', ownerId)) .order('desc') .take(limit ?? 25); }, }); export const createInternal = internalMutation({ args: { spoonId: v.id('spoons'), ownerId: v.id('users'), syncRunId: v.optional(v.id('syncRuns')), model: v.string(), status: reviewStatus, reviewType, inputSummary: v.string(), }, handler: async (ctx, args): Promise> => { const now = Date.now(); return await ctx.db.insert('aiReviews', { ...args, risk: 'unknown', compatible: false, requiresHumanReview: true, recommendedAction: 'unknown', createdAt: now, updatedAt: now, }); }, }); export const completeInternal = internalMutation({ args: { reviewId: v.id('aiReviews'), outputSummary: v.string(), risk, compatible: v.boolean(), requiresHumanReview: v.boolean(), recommendedAction, potentialConflicts: v.array(v.string()), importantFiles: v.array(v.string()), reasoningSummary: v.string(), }, handler: async (ctx, args) => { const review = await ctx.db.get(args.reviewId); if (!review) throw new ConvexError('AI review not found.'); const patch: Partial> = { status: 'completed', outputSummary: args.outputSummary, risk: args.risk, compatible: args.compatible, requiresHumanReview: args.requiresHumanReview, recommendedAction: args.recommendedAction, potentialConflicts: args.potentialConflicts, importantFiles: args.importantFiles, reasoningSummary: args.reasoningSummary, updatedAt: Date.now(), completedAt: Date.now(), }; await ctx.db.patch(args.reviewId, patch); return review; }, }); export const failInternal = internalMutation({ args: { reviewId: v.id('aiReviews'), error: v.string() }, handler: async (ctx, { reviewId, error }) => { await ctx.db.patch(reviewId, { status: 'failed', error, updatedAt: Date.now(), }); return { success: true }; }, });