const VALID_PROFILES = ['auto', 'fast', 'quality'] as const; const VALID_OUTPUT_FORMATS = ['text', 'markdown', 'json'] as const; const VALID_ASK_POLICIES = ['low', 'high'] as const; const VALID_PRIORITIES = ['low', 'medium', 'high'] as const; const MAX_BODY_LENGTH = 100_000; const MAX_TITLE_LENGTH = 200; const MAX_COMMENT_LENGTH = 100_000; export function parseTaskId(raw: string): number | null { const n = Number(raw); if (!Number.isInteger(n) || n <= 0) return null; return n; } export interface ValidatedCreateTask { body: string; title?: string; piece?: string; profile?: typeof VALID_PROFILES[number]; outputFormat?: typeof VALID_OUTPUT_FORMATS[number]; askPolicy?: typeof VALID_ASK_POLICIES[number]; priority?: typeof VALID_PRIORITIES[number]; attachments?: Array<{ name: string; contentBase64: string }>; } type ValidationResult = | { valid: true; data: ValidatedCreateTask } | { valid: false; error: string }; export function validateCreateTaskBody(raw: unknown): ValidationResult { if (!raw || typeof raw !== 'object') { return { valid: false, error: 'Request body must be an object' }; } const obj = raw as Record; if (typeof obj.body !== 'string' || obj.body.trim().length === 0) { return { valid: false, error: 'body is required' }; } if (obj.body.length > MAX_BODY_LENGTH) { return { valid: false, error: `body must be ${MAX_BODY_LENGTH} characters or less` }; } if (obj.title !== undefined && obj.title !== null) { if (typeof obj.title !== 'string') { return { valid: false, error: 'title must be a string' }; } if (obj.title.length > MAX_TITLE_LENGTH) { return { valid: false, error: `title must be ${MAX_TITLE_LENGTH} characters or less` }; } } if (obj.profile !== undefined && obj.profile !== null) { if (!(VALID_PROFILES as readonly string[]).includes(String(obj.profile))) { return { valid: false, error: `profile must be one of: ${VALID_PROFILES.join(', ')}` }; } } if (obj.outputFormat !== undefined && obj.outputFormat !== null) { if (!(VALID_OUTPUT_FORMATS as readonly string[]).includes(String(obj.outputFormat))) { return { valid: false, error: `outputFormat must be one of: ${VALID_OUTPUT_FORMATS.join(', ')}` }; } } if (obj.askPolicy !== undefined && obj.askPolicy !== null) { if (!(VALID_ASK_POLICIES as readonly string[]).includes(String(obj.askPolicy))) { return { valid: false, error: `askPolicy must be one of: ${VALID_ASK_POLICIES.join(', ')}` }; } } if (obj.priority !== undefined && obj.priority !== null) { if (!(VALID_PRIORITIES as readonly string[]).includes(String(obj.priority))) { return { valid: false, error: `priority must be one of: ${VALID_PRIORITIES.join(', ')}` }; } } return { valid: true, data: { body: obj.body as string, title: obj.title as string | undefined, piece: obj.piece as string | undefined, profile: obj.profile as ValidatedCreateTask['profile'], outputFormat: obj.outputFormat as ValidatedCreateTask['outputFormat'], askPolicy: obj.askPolicy as ValidatedCreateTask['askPolicy'], priority: obj.priority as ValidatedCreateTask['priority'], attachments: obj.attachments as ValidatedCreateTask['attachments'], }, }; } export function validateCommentBody(raw: unknown): { valid: true; body: string; author: string; attachments?: Array<{ name: string; contentBase64: string }> } | { valid: false; error: string } { if (!raw || typeof raw !== 'object') { return { valid: false, error: 'Request body must be an object' }; } const obj = raw as Record; const body = String(obj.body ?? '').trim(); if (!body) { return { valid: false, error: 'body is required' }; } if (body.length > MAX_COMMENT_LENGTH) { return { valid: false, error: `body must be ${MAX_COMMENT_LENGTH} characters or less` }; } const author = String(obj.author ?? 'user').trim() || 'user'; const attachments = Array.isArray(obj.attachments) ? obj.attachments as Array<{ name: string; contentBase64: string }> : undefined; return { valid: true, body, author, attachments }; } export type ValidatedFeedback = { rating: 'good' | 'bad'; tags: string[]; comment: string | null; }; type FeedbackValidationResult = | { valid: true; data: ValidatedFeedback } | { valid: false; error: string }; export function validateFeedbackBody(raw: unknown): FeedbackValidationResult { if (!raw || typeof raw !== 'object') { return { valid: false, error: 'Request body must be an object' }; } const obj = raw as Record; if (obj.rating !== 'good' && obj.rating !== 'bad') { return { valid: false, error: "rating must be 'good' or 'bad'" }; } if (!Array.isArray(obj.tags) || obj.tags.some((t: unknown) => typeof t !== 'string')) { return { valid: false, error: 'tags must be an array of strings' }; } if (obj.tags.length > 10) { return { valid: false, error: 'tags must have at most 10 items' }; } const comment = obj.comment != null ? String(obj.comment) : null; if (comment && comment.length > 1000) { return { valid: false, error: 'comment must be at most 1000 characters' }; } return { valid: true, data: { rating: obj.rating, tags: obj.tags as string[], comment }, }; }