feat(backend): Ollama embeddings preflight on session create/resume (503 hard-fail)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 19:37:39 +02:00
co-authored by Claude Sonnet 4.6
parent a0af089d00
commit 90a26dafce
5 changed files with 59 additions and 3 deletions
+4 -1
View File
@@ -43,7 +43,10 @@ export function buildApp(config: AppConfig, deps?: BuildAppDeps): FastifyInstanc
app.addHook("preHandler", authPreHandler(config.authMode));
app.get("/health", async () => ({ status: "ok" }));
sessionRoutes(app, { mgr, tht: tht as ThtRunner, hub, getSettings });
sessionRoutes(app, {
mgr, tht: tht as ThtRunner, hub, getSettings,
ollamaEnsureTimeoutSec: Math.round(config.ollamaEnsureTimeoutMs / 1000),
});
sqlRoutes(app, { tht: tht as ThtRunner });
metaRoutes(app, { harnessDir: config.harnessDir, listModels });
settingsRoutes(app, { cfg: config, listModels });
+2
View File
@@ -4,6 +4,7 @@ export interface AppConfig {
defaults: { provider?: string; model?: string; thinking?: string };
maxPiProcesses: number;
settingsFile: string;
ollamaEnsureTimeoutMs: number;
}
export function loadConfig(env: Record<string, string | undefined>): AppConfig {
return {
@@ -15,5 +16,6 @@ export function loadConfig(env: Record<string, string | undefined>): AppConfig {
defaults: { provider: env.PI_PROVIDER, model: env.PI_MODEL, thinking: env.PI_THINKING },
maxPiProcesses: Number(env.MAX_PI_PROCESSES ?? 4),
settingsFile: env.SETTINGS_FILE ?? "data/settings.json",
ollamaEnsureTimeoutMs: Number(env.OLLAMA_ENSURE_TIMEOUT_MS ?? 60000),
};
}
+5 -1
View File
@@ -7,11 +7,13 @@ import { getUser } from "../auth/auth.js";
export function sessionRoutes(
app: FastifyInstance,
d: { mgr: PiProcessManager; tht: ThtRunner; hub: SseHub; getSettings: () => Settings },
d: { mgr: PiProcessManager; tht: ThtRunner; hub: SseHub; getSettings: () => Settings; ollamaEnsureTimeoutSec: number },
) {
app.post("/sessions", async (req, reply) => {
const b = req.body as { question: string; name?: string };
const s = d.getSettings();
const ensure = await d.tht.ollamaEnsure(s.workspace ?? "", d.ollamaEnsureTimeoutSec);
if (!ensure.ok) return reply.code(503).send({ error: ensure.error ?? "Ollama/embeddings non disponibili" });
// Settings (global) supply workspace/provider/model/thinking. The new-question
// form sends only the question text. `workspace` selects the tht `-c <config>`.
const { id } = await d.tht.sessionNew({
@@ -48,6 +50,8 @@ export function sessionRoutes(
});
app.post("/sessions/:id/resume", async (req, reply) => {
const id = (req.params as any).id;
const ensure = await d.tht.ollamaEnsure(d.getSettings().workspace ?? "", d.ollamaEnsureTimeoutSec);
if (!ensure.ok) return reply.code(503).send({ error: ensure.error ?? "Ollama/embeddings non disponibili" });
const manifest = (await d.tht.sessionShow(id)) as { status?: string; archived?: boolean } | null;
if (manifest?.status === "finalized" || manifest?.archived) {
return reply.code(409).send({ error: "sessione in sola lettura (finalizzata o archiviata)" });