Files
ThothII/backend/src/runtime/readiness-manager.ts
T

49 lines
1.6 KiB
TypeScript

import type { OllamaEnsureResult, ThtRunner } from "../tht/tht-runner.js";
import type { PrincipalContext } from "../auth/principal.js";
interface ReadyEntry {
expiresAt: number;
result: OllamaEnsureResult;
}
/**
* Deduplicates embedding readiness checks and keeps only short-lived successes.
* Failures are deliberately not cached so a submit can retry after a transient outage.
*/
export class ReadinessManager {
private inFlight = new Map<string, Promise<OllamaEnsureResult>>();
private ready = new Map<string, ReadyEntry>();
constructor(
private tht: ThtRunner,
private timeoutSec: number,
private ttlMs = 60_000,
private now: () => number = Date.now,
) {}
ensure(workspace = "", principal?: PrincipalContext): Promise<OllamaEnsureResult> {
const key = `${principal?.issuer ?? ""}\0${principal?.subject ?? ""}\0${workspace}`;
const cached = this.ready.get(key);
if (cached && cached.expiresAt > this.now()) return Promise.resolve(cached.result);
if (cached) this.ready.delete(key);
const current = this.inFlight.get(key);
if (current) return current;
const runner = principal && typeof (this.tht as any).withPrincipal === "function"
? this.tht.withPrincipal(principal) : this.tht;
const pending = runner.ollamaEnsure(workspace, this.timeoutSec)
.then((result) => {
if (result.ok) {
this.ready.set(key, { result, expiresAt: this.now() + this.ttlMs });
}
return result;
})
.finally(() => {
if (this.inFlight.get(key) === pending) this.inFlight.delete(key);
});
this.inFlight.set(key, pending);
return pending;
}
}