mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
The summary and suggest routes accept `Prefer: respond-async`: after the same quick refusals they answer 202 with a run id, and the background document AI worker runs the read under two new run kinds (DOCUMENT_SUMMARY, DOCUMENT_SUGGEST, migration 0370). Without the header both routes answer synchronously as before; both paths run one shared body, so they cannot drift. The worker asks the capability for the record and re-checks the wire for the provider it picks before every read. A summary that is to be stored marks the document PENDING while queued, READY when it lands, and UNAVAILABLE when the run fails or the write does not land. A second request attaches to a live run only when it would produce the same thing. The vault asks for both in the background and shows the same calm waiting line the index uses, worded so a transient read never promises that leaving the page keeps it. The egress guard and the route inventory now hold every run kind to its dispatcher re-check and its capability.
75 lines
2.3 KiB
TypeScript
75 lines
2.3 KiB
TypeScript
/**
|
|
* Pieces the summary, suggest and extract run bodies share: the provider pick
|
|
* types, the failure shape, and the answer for a stored original that could
|
|
* not be turned into a provider input.
|
|
*
|
|
* Every failure here carries the status, message and `documents.inbound.*`
|
|
* code the synchronous route has always answered with, so the background run
|
|
* reports exactly what the request would have.
|
|
*/
|
|
import type {
|
|
requireDocumentTextProvider,
|
|
requireDocumentVisionProvider,
|
|
} from "@/lib/documents/provider-order";
|
|
import type { VisionInput } from "@/lib/documents/ai-route-support";
|
|
|
|
import type { AiRunOutcome } from "./types";
|
|
|
|
export type DocumentVisionPick = Awaited<
|
|
ReturnType<typeof requireDocumentVisionProvider>
|
|
>;
|
|
export type DocumentTextPick = Awaited<
|
|
ReturnType<typeof requireDocumentTextProvider>
|
|
>;
|
|
|
|
/** Who asked: a request (with its client address) or a background run. */
|
|
export interface RunOrigin {
|
|
ipAddress: string | null;
|
|
worker: boolean;
|
|
}
|
|
|
|
/** A failure in the shape the route would answer with. */
|
|
export const runFailure = (f: {
|
|
status: number;
|
|
message: string;
|
|
errorCode: string;
|
|
}): AiRunOutcome<never> => ({ ok: false, ...f });
|
|
|
|
/** The provider error every document read answers with (422 parse, 502 wire). */
|
|
export function unreadableDocument(parseError: boolean): AiRunOutcome<never> {
|
|
return runFailure({
|
|
status: parseError ? 422 : 502,
|
|
message: "Couldn't read the document. Try a clearer copy.",
|
|
errorCode: "documents.inbound.extractFailed",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* A stored original that could not become a provider input, as the summary
|
|
* and suggest routes have always answered it.
|
|
*/
|
|
export function visionPreparationFailure(
|
|
reason: Extract<VisionInput, { ok: false }>["reason"],
|
|
): AiRunOutcome<never> {
|
|
if (reason === "pdfNeedsAnthropic") {
|
|
return runFailure({
|
|
status: 422,
|
|
message:
|
|
"PDF scanning needs a Claude vision provider; use local OCR instead.",
|
|
errorCode: "documents.inbound.pdfNeedsAnthropic",
|
|
});
|
|
}
|
|
if (reason === "fileType") {
|
|
return runFailure({
|
|
status: 422,
|
|
message: "This document can't be scanned. Use local OCR (text mode).",
|
|
errorCode: "documents.inbound.fileType",
|
|
});
|
|
}
|
|
return runFailure({
|
|
status: 422,
|
|
message: "Couldn't read the stored document.",
|
|
errorCode: "documents.inbound.extractFailed",
|
|
});
|
|
}
|