import { getProvider, getProviderModelName } from "@/lib/llm/provider"; import { buildFocusedDeconstructPrompt, focusedDeconstructJsonSchema, validateFocusedDeconstructSchema, } from "@/lib/graph/focused-investigation"; import { withAuthenticatedApi } from "@/lib/supabase/api-auth.js"; async function post(request) { let targetNodeId = null; let startedAt = null; try { const body = await request.json(); targetNodeId = body.targetNodeId ?? null; if (!body.targetNodeId || typeof body.targetNodeId !== "string") { return Response.json( { error: "Request must include a 'targetNodeId' string field" }, { status: 400 }, ); } if (!body.targetLabel || typeof body.targetLabel !== "string") { return Response.json( { error: "Request must include a 'targetLabel' string field" }, { status: 400 }, ); } if (!body.targetDescription || typeof body.targetDescription !== "string") { return Response.json( { error: "Request must include a 'targetDescription' string field" }, { status: 400 }, ); } if (!body.centralStatement || typeof body.centralStatement !== "string") { return Response.json( { error: "Request must include a 'centralStatement' string field" }, { status: 400 }, ); } if (!body.question || typeof body.question !== "string") { return Response.json( { error: "Request must include a 'question' string field" }, { status: 400 }, ); } if (!body.answer || typeof body.answer !== "string") { return Response.json( { error: "Request must include an 'answer' string field" }, { status: 400 }, ); } const REQUEST_LENGTH_LIMITS = { answer: 10000, question: 2048, centralStatement: 2048, targetLabel: 2048, targetDescription: 2048, }; for (const [field, limit] of Object.entries(REQUEST_LENGTH_LIMITS)) { if (body[field] && body[field].length > limit) { return Response.json( { error: `Request field "${field}" exceeds maximum length of ${limit} characters` }, { status: 400 }, ); } } const prompt = buildFocusedDeconstructPrompt({ targetLabel: body.targetLabel, targetDescription: body.targetDescription, centralStatement: body.centralStatement, question: body.question, answer: body.answer, }); const provider = getProvider(); const modelName = getProviderModelName(); startedAt = Date.now(); console.info("[api/focused-investigation/deconstruct] start", { targetNodeId, modelName, providerMode: process.env.CONFIDENCE_ENGINE_EXPERIMENT_PROVIDER ?? "ollama", startedAt, }); const wrapper = await provider.generateReconstruction( prompt, modelName, focusedDeconstructJsonSchema, ); const elapsedMs = Date.now() - startedAt; // Unwrap the semantic deconstruction from the provider envelope. const deconstruction = wrapper.response; console.info("[api/focused-investigation/deconstruct] provider success", { targetNodeId, elapsedMs, providerApiPath: wrapper.providerApiPath ?? null, responsePresent: Boolean(deconstruction), responseKeys: deconstruction && typeof deconstruction === "object" ? Object.keys(deconstruction) : [], }); // Validate schema (required fields present, no graph-mutation fields) const validationErrors = validateFocusedDeconstructSchema(deconstruction); console.info("[api/focused-investigation/deconstruct] validation", { targetNodeId, schemaValid: validationErrors.length === 0, responseKeys: deconstruction && typeof deconstruction === "object" ? Object.keys(deconstruction) : [], validationErrors, }); if (validationErrors.length > 0) { console.info("[api/focused-investigation/deconstruct] end", { targetNodeId, status: 502, elapsedMs, }); return Response.json( { success: false, error: "Focused deconstruction result did not match expected schema", validationErrors, targetNodeId: body.targetNodeId, elapsedMs, }, { status: 502 }, ); } const response = Response.json({ success: true, targetNodeId: body.targetNodeId, observations: deconstruction.observations, uncertainties: deconstruction.uncertainties, assumptions: deconstruction.assumptions, relationships: deconstruction.relationships, possibleFollowUpQuestions: deconstruction.possibleFollowUpQuestions, elapsedMs, }); console.info("[api/focused-investigation/deconstruct] end", { targetNodeId, status: 200, elapsedMs, }); return response; } catch (e) { const elapsedMs = startedAt == null ? null : Date.now() - startedAt; console.error("[api/focused-investigation/deconstruct] provider failure", { targetNodeId, elapsedMs, errorName: e?.name ?? "Error", errorMessage: e?.message ?? "Unknown server error", statusCode: e?.statusCode ?? e?.status ?? null, providerApiPath: e?.providerApiPath ?? null, errorCode: e?.code ?? null, errorParam: e?.param ?? null, }); console.info("[api/focused-investigation/deconstruct] end", { targetNodeId, status: e?.code === "PROVIDER_UNAVAILABLE" ? 503 : 500, elapsedMs, }); if (e?.code === "PROVIDER_UNAVAILABLE") { return Response.json( { error: "Reasoning service is temporarily unavailable." }, { status: 503 }, ); } return Response.json( { error: "Reasoning request could not be completed." }, { status: 500 }, ); } } export const POST = withAuthenticatedApi(post);