feat: initial public release (MAESTRO)
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import type { Repository } from '../db/repository.js';
|
||||
import type { WorkerDef } from '../config.js';
|
||||
import type { BackendStatusRegistry, NodeStatus } from '../engine/backend-status-registry.js';
|
||||
|
||||
export interface WorkerStatusBackendRow {
|
||||
/** Stable identifier from the upstream /health response (deployment id). */
|
||||
id: string;
|
||||
/** idle = registry says zero busy slots, running = at least one in-flight. */
|
||||
state: 'idle' | 'running';
|
||||
/** Number of in-flight slots reported by the registry. */
|
||||
busySlots: number;
|
||||
/** Total slot capacity. 0 when the registry hasn't probed yet. */
|
||||
totalSlots: number;
|
||||
/** false when the most recent /health probe failed. null when unprobed. */
|
||||
online: boolean | null;
|
||||
}
|
||||
|
||||
export interface WorkerStatusRow {
|
||||
id: string;
|
||||
name: string;
|
||||
roles: string[];
|
||||
state: 'idle' | 'running';
|
||||
/** True when this row represents a `proxy: true` worker (LiteLLM / AAO Gateway). */
|
||||
proxy: boolean;
|
||||
/**
|
||||
* Slot pressure from the BackendStatusRegistry, populated for direct
|
||||
* workers when the registry has seen at least one probe cycle and a
|
||||
* matching `nodeId === worker.id` row exists. Proxy workers leave
|
||||
* these undefined — the meaningful breakdown lives in `backends[]`
|
||||
* (per-backend, since the proxy itself doesn't have its own
|
||||
* /slots-style busy figure).
|
||||
*/
|
||||
busySlots?: number;
|
||||
totalSlots?: number;
|
||||
/** Probe liveness. Same gating as busySlots/totalSlots. */
|
||||
online?: boolean;
|
||||
/**
|
||||
* Per-backend rows for proxy workers — populated when a
|
||||
* BackendStatusRegistry is wired and the registry has seen at least
|
||||
* one probe cycle for this worker. Omitted (undefined, not empty)
|
||||
* for direct workers so the UI can distinguish "no backends because
|
||||
* this is a direct worker" from "proxy worker with zero backends
|
||||
* reported".
|
||||
*/
|
||||
backends?: WorkerStatusBackendRow[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the per-worker status list for the Side Info Panel.
|
||||
*
|
||||
* Proxy workers fan out into a `backends[]` list when a
|
||||
* `BackendStatusRegistry` is supplied — the Worker widget renders the
|
||||
* tree at the same granularity as the Node Status widget, so an
|
||||
* operator can see *which* backend behind a LiteLLM / AAO Gateway
|
||||
* front is currently in use rather than just "the proxy is busy".
|
||||
*
|
||||
* Privacy: returns idle/running booleans + slot counts only. Never job
|
||||
* ids, titles, or owners, since the panel is shown to all users in a
|
||||
* multi-tenant deployment.
|
||||
*/
|
||||
export async function collectWorkerStatuses(
|
||||
repo: Repository,
|
||||
workers: WorkerDef[],
|
||||
registry: Pick<BackendStatusRegistry, 'getAll'> | null = null,
|
||||
): Promise<WorkerStatusRow[]> {
|
||||
// Build a workerId → NodeStatus[] map once per call so we don't
|
||||
// O(N*M) the registry snapshot per worker. registry.getAll() copies
|
||||
// its internal cache, so calling it once is cheap.
|
||||
const byWorker = new Map<string, NodeStatus[]>();
|
||||
if (registry) {
|
||||
for (const ns of registry.getAll()) {
|
||||
const list = byWorker.get(ns.workerId);
|
||||
if (list) list.push(ns);
|
||||
else byWorker.set(ns.workerId, [ns]);
|
||||
}
|
||||
}
|
||||
|
||||
return workers.map((w) => {
|
||||
const isProxy = w.proxy === true;
|
||||
const row: WorkerStatusRow = {
|
||||
id: w.id,
|
||||
name: w.id,
|
||||
roles: w.roles ?? [],
|
||||
state: repo.isWorkerBusy(w.id) ? 'running' : 'idle',
|
||||
proxy: isProxy,
|
||||
};
|
||||
if (isProxy && registry) {
|
||||
// Filter to backend-source rows only — the registry also stores a
|
||||
// self-row for the proxy worker itself (source='proxy', nodeId =
|
||||
// workerId) which would otherwise show up duplicated as a child
|
||||
// of itself.
|
||||
const rows = (byWorker.get(w.id) ?? []).filter((ns) => ns.nodeId !== w.id);
|
||||
row.backends = rows.map((ns) => ({
|
||||
id: ns.nodeId,
|
||||
state: ns.busySlots > 0 ? 'running' : 'idle',
|
||||
busySlots: ns.busySlots,
|
||||
totalSlots: ns.totalSlots,
|
||||
online: ns.online,
|
||||
}));
|
||||
} else if (!isProxy && registry) {
|
||||
// Direct workers: the registry stores one row keyed by the
|
||||
// worker id (source='direct', populated from llama-server
|
||||
// /slots). Surface its slot pressure at the row level so the
|
||||
// UI can render `(busy/total)` next to the state badge — same
|
||||
// signal proxy backends get, just one level higher in the
|
||||
// tree because direct workers have no expansion.
|
||||
const selfRow = (byWorker.get(w.id) ?? []).find((ns) => ns.nodeId === w.id);
|
||||
if (selfRow) {
|
||||
row.busySlots = selfRow.busySlots;
|
||||
row.totalSlots = selfRow.totalSlots;
|
||||
row.online = selfRow.online;
|
||||
// Re-derive state from the probe too — it sees in-flight
|
||||
// requests that didn't go through the local jobs table
|
||||
// (e.g. anything dispatched outside AAO). `repo.isWorkerBusy`
|
||||
// alone misses those.
|
||||
if (selfRow.busySlots > 0) row.state = 'running';
|
||||
}
|
||||
}
|
||||
return row;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user