148 lines
3.9 KiB
JavaScript
148 lines
3.9 KiB
JavaScript
import { updateCase, reconsiderCompletedEpisode } from "@/lib/graph/orchestrator.js";
|
|
import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
|
|
import { prepareCompletedEpisode } from "@/lib/graph/episode-preparation.js";
|
|
import { updateCaseEpisodeRequestSchema } from "@/lib/graph/schema.js";
|
|
import { withAuthenticatedApi } from "@/lib/supabase/api-auth.js";
|
|
|
|
function mapFailureStatus(result) {
|
|
switch (result?.stage) {
|
|
case "request_validation":
|
|
case "graph_validation":
|
|
return 400;
|
|
case "provider":
|
|
return 502;
|
|
case "proposal_validation":
|
|
case "proposal_compatibility":
|
|
case "application":
|
|
return 422;
|
|
case "result_validation":
|
|
return 500;
|
|
default:
|
|
return 500;
|
|
}
|
|
}
|
|
|
|
function buildFailureResponse(result) {
|
|
return {
|
|
success: false,
|
|
stage: result?.stage ?? "internal",
|
|
error: result?.error ?? "Update case failed",
|
|
validationErrors: result?.validationErrors,
|
|
graphValidationErrors: result?.graphValidationErrors,
|
|
proposalErrors: result?.proposalErrors,
|
|
providerErrors: result?.providerErrors,
|
|
errors: result?.errors,
|
|
diagnostics: result?.diagnostics,
|
|
};
|
|
}
|
|
|
|
async function post(request) {
|
|
try {
|
|
const body = await request.json();
|
|
const isEpisodeMode = body?.episodeMode === true;
|
|
|
|
if (isEpisodeMode) {
|
|
const parsed = updateCaseEpisodeRequestSchema.safeParse(body);
|
|
if (!parsed.success) {
|
|
return Response.json(
|
|
{
|
|
success: false,
|
|
stage: "request_validation",
|
|
error: "Invalid episode request",
|
|
validationErrors: parsed.error.issues,
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
return await handleEpisodeMode(body.situationGraph, body);
|
|
}
|
|
|
|
const result = await updateCase(body, { applyProposal: true });
|
|
|
|
if (result.success) {
|
|
return Response.json(result, { status: 200 });
|
|
}
|
|
|
|
return Response.json(buildFailureResponse(result), {
|
|
status: mapFailureStatus(result),
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof SyntaxError) {
|
|
return Response.json(
|
|
{
|
|
success: false,
|
|
stage: "request_validation",
|
|
error: "Invalid JSON request body",
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
return Response.json(
|
|
{
|
|
success: false,
|
|
stage: "internal",
|
|
error: "Internal server error",
|
|
},
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|
|
|
|
export const POST = withAuthenticatedApi(post);
|
|
|
|
/** Server-side completed-episode reconsideration flow. */
|
|
async function handleEpisodeMode(situationGraph, body) {
|
|
const prepared = prepareCompletedEpisode({
|
|
situationGraph,
|
|
targetNodeId: body.targetNodeId,
|
|
contributions: body.contributions ?? [],
|
|
findings: body.findings,
|
|
});
|
|
|
|
if (!prepared?.turns?.length && !prepared?.eligibleCanonicalFindings?.length) {
|
|
return Response.json(
|
|
{ success: false, stage: "preparation", error: "no_episodic_content" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const reasoning = await reconsiderCompletedEpisode(prepared);
|
|
if (!reasoning.success) {
|
|
return Response.json(
|
|
buildFailureResponse(reasoning),
|
|
{ status: mapFailureStatus(reasoning) },
|
|
);
|
|
}
|
|
|
|
const application = await applyValidatedProposal({
|
|
situationGraph,
|
|
proposal: reasoning.proposal,
|
|
evidenceContext: {
|
|
isCompletedEpisode: true,
|
|
episodeEvidence: prepared,
|
|
},
|
|
});
|
|
|
|
if (!application.success) {
|
|
return Response.json(
|
|
buildFailureResponse(application),
|
|
{ status: mapFailureStatus(application) },
|
|
);
|
|
}
|
|
|
|
const resolvedNodeIds = new Set(application.updatedSituationGraph.resolvedNodeIds ?? []);
|
|
resolvedNodeIds.add(body.targetNodeId);
|
|
const updatedSituationGraph = {
|
|
...application.updatedSituationGraph,
|
|
resolvedNodeIds: [...resolvedNodeIds],
|
|
};
|
|
|
|
return Response.json({
|
|
success: true,
|
|
updatedSituationGraph,
|
|
proposal: reasoning.proposal,
|
|
}, { status: 200 });
|
|
}
|