72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import type { InferenceProvider, HealthResult } from '../ai/InferenceProvider.js';
|
|
|
|
export type HealthTelemetryEvent =
|
|
| { kind: 'ollama_unreachable'; reason: string }
|
|
| { kind: 'ollama_recovered'; downtimeMs: number }
|
|
| { kind: 'ollama_recheck_manual' };
|
|
|
|
export interface HealthCheckerOptions {
|
|
intervalMs?: number;
|
|
onUpdate?: (status: HealthResult) => void;
|
|
onTelemetry?: (event: HealthTelemetryEvent) => void;
|
|
now?: () => number;
|
|
}
|
|
|
|
const DEFAULT_INTERVAL_MS = 60_000;
|
|
|
|
export class HealthChecker {
|
|
private last: HealthResult = { ok: true };
|
|
private timer: NodeJS.Timeout | null = null;
|
|
private unreachableSince: number | null = null;
|
|
private intervalMs: number;
|
|
private now: () => number;
|
|
|
|
constructor(
|
|
private provider: InferenceProvider,
|
|
private opts: HealthCheckerOptions = {}
|
|
) {
|
|
this.intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
this.now = opts.now ?? Date.now;
|
|
}
|
|
|
|
async runOnce(opts?: { manual?: boolean }): Promise<HealthResult> {
|
|
if (opts?.manual === true) {
|
|
this.opts.onTelemetry?.({ kind: 'ollama_recheck_manual' });
|
|
}
|
|
const next = await this.provider.healthCheck();
|
|
const prev = this.last;
|
|
const okChanged = prev.ok !== next.ok;
|
|
const reasonChanged = prev.reason !== next.reason;
|
|
if (okChanged) {
|
|
if (next.ok === false) {
|
|
this.unreachableSince = this.now();
|
|
this.opts.onTelemetry?.({ kind: 'ollama_unreachable', reason: next.reason ?? 'unknown' });
|
|
} else {
|
|
const downtimeMs = this.unreachableSince !== null ? this.now() - this.unreachableSince : 0;
|
|
this.unreachableSince = null;
|
|
this.opts.onTelemetry?.({ kind: 'ollama_recovered', downtimeMs });
|
|
}
|
|
this.opts.onUpdate?.(next);
|
|
} else if (reasonChanged) {
|
|
this.opts.onUpdate?.(next);
|
|
}
|
|
this.last = next;
|
|
return next;
|
|
}
|
|
|
|
start(): void {
|
|
if (this.timer !== null) return;
|
|
void this.runOnce();
|
|
this.timer = setInterval(() => { void this.runOnce(); }, this.intervalMs);
|
|
}
|
|
|
|
stop(): void {
|
|
if (this.timer !== null) {
|
|
clearInterval(this.timer);
|
|
this.timer = null;
|
|
}
|
|
}
|
|
|
|
lastStatus(): HealthResult { return this.last; }
|
|
}
|