import { HttpError } from "./errors.ts"; export function pickSchemaFields( definicion: any, campos: string[], ) { if (!definicion || definicion.type !== "object" || !definicion.properties) { return definicion; } const extra = { properties: { "ai-message": { type: "string", description: "Mensaje breve para el usuario final confirmando qué se mejoró y qué se hizo.", examples: [ "Listo: mejoré la redacción del perfil de ingreso y propuse un tema de investigación alineado al plan.", ], }, "is-refusal": { type: "boolean", description: "Indica si el plan fue rechazado por el modelo. En caso de ser true, se espera un mensaje de rechazo en `ai-message`.", }, }, }; const out = structuredClone(definicion); // Si piden campos, filtramos propiedades/required a esos campos const entries = Object.entries(out.properties).filter(([k]) => campos.includes(k) ); out.properties = Object.fromEntries(entries); if (Array.isArray(out.required)) { out.required = out.required.filter((k: string) => campos.includes(k)); } // Siempre agregamos ai-message out.properties = { ...out.properties, ...extra.properties }; out.required = Array.isArray(out.required) ? [...new Set([...out.required, ...Object.keys(extra.properties)])] : Object.keys(extra.properties); return out; } export function pickSchemaAsignaturaFields( definicion: any, campos: string[], ) { // 1. Validación inicial if (!definicion || definicion.type !== "object" || !definicion.properties) { return { type: "object", properties: { "ai-message": { type: "string" }, "is_refusal": { type: "boolean" } }, required: ["ai-message", "is_refusal"], additionalProperties: false }; } // 2. Filtrar solo las propiedades técnicas que el usuario pidió const filteredProperties = Object.fromEntries( Object.entries(definicion.properties).filter(([k]) => campos.includes(k)) ); // 3. RECONSTRUIR el esquema incluyendo SIEMPRE los campos de control const finalSchema = { type: "object", properties: { "ai-message": { type: "string", description: "Tu respuesta conversacional dirigida al profesor." }, "is_refusal": { type: "boolean", description: "Indica si la solicitud es inapropiada o no relacionada." }, ...filteredProperties // Aquí entran objetivo, contenido, etc. }, // Forzamos que ai-message e is_refusal sean obligatorios siempre required: ["ai-message", "is_refusal", ...Object.keys(filteredProperties)], additionalProperties: false }; return finalSchema; } export function safePlanForPrompt(plan: any) { const copy = structuredClone(plan); if (copy?.estructuras_plan) delete copy.estructuras_plan; return copy; } export function assertUuid(v: string, name: string) { // validación ligera if (!v || typeof v !== "string" || v.length < 10) { throw new HttpError(400, "bad_input", `Invalid ${name}`); } }