mirror of
https://github.com/hypit-ai/hypit.git
synced 2026-11-06 18:57:21 +00:00
133 lines
5.6 KiB
TypeScript
133 lines
5.6 KiB
TypeScript
import type {
|
|
CreateLocalRuntimeControlOptions,
|
|
LocalRuntimeControl,
|
|
} from "./types.js";
|
|
import { buildExecutionActivity } from "./execution.js";
|
|
import type {
|
|
BuildSnapshot,
|
|
OperationSnapshot,
|
|
} from "@hypit/hypit/runtime";
|
|
import type { PendingBuildSubmission } from "./submission.js";
|
|
import type { BuildCatalogEntry } from "./catalog.js";
|
|
import type { BuildExecutionSnapshot } from "./execution.js";
|
|
import type { BuildView } from "./host-api.js";
|
|
import { buildIdCreatedAt } from "@hypit/hypit/protocol";
|
|
|
|
function buildView(input: {
|
|
readonly build: string;
|
|
readonly snapshot?: BuildSnapshot;
|
|
readonly catalog?: BuildCatalogEntry;
|
|
readonly submission?: PendingBuildSubmission;
|
|
readonly execution?: BuildExecutionSnapshot;
|
|
readonly operations: readonly OperationSnapshot[];
|
|
readonly commands: readonly import("@hypit/hypit/runtime").CommandExecutionReceipt[];
|
|
}): BuildView | undefined {
|
|
if (input.submission === undefined && input.execution === undefined) return undefined;
|
|
const createdAt = buildIdCreatedAt(input.build);
|
|
if (createdAt === undefined) throw new Error(`Active Build ${input.build} has no ordered public id`);
|
|
const runtimeActivity = input.execution === undefined ? undefined : buildExecutionActivity(input.execution);
|
|
const activity: BuildView["activity"] = input.submission !== undefined
|
|
? "submitting"
|
|
: runtimeActivity!;
|
|
const names = new Map(input.catalog?.publishedOutputs.map((item) => [item.ref.id, item.name]) ?? []);
|
|
const targetRefs = input.catalog?.targets ?? input.snapshot?.state.targets.map((target) => ({
|
|
kind: "logical-output" as const,
|
|
id: target.output,
|
|
})) ?? [];
|
|
const targets = targetRefs.map((target) => {
|
|
const name = names.get(target.id);
|
|
if (name === undefined) throw new Error(`Build ${input.build} target ${target.id} has no published Output name`);
|
|
return name;
|
|
});
|
|
const requests = input.snapshot === undefined ? undefined : {
|
|
total: input.snapshot.definition.plan.steps.reduce(
|
|
(total, step) => total + Object.keys(step.needs).length,
|
|
0,
|
|
),
|
|
completed: input.snapshot.facts.filter((fact) => fact.kind === "need-applied").length,
|
|
};
|
|
return {
|
|
id: input.build,
|
|
createdAt,
|
|
activity,
|
|
...(input.execution?.decision === undefined ? {} : { outcome: input.execution.decision.outcome }),
|
|
...(input.execution?.attention === undefined ? {} : {
|
|
issue: { scope: input.execution.attention.step, message: input.execution.attention.error },
|
|
}),
|
|
cancellationRequested: input.execution?.stop?.cause === "user-cancelled",
|
|
...(input.execution?.stop === undefined ? {} : { stop: input.execution.stop }),
|
|
...(input.catalog?.source === undefined ? {} : { source: input.catalog.source }),
|
|
...(input.catalog?.run === undefined ? {} : { run: input.catalog.run }),
|
|
targets,
|
|
...(requests === undefined ? {} : { requests }),
|
|
acceptedRecords: input.snapshot?.state.records.length ?? 0,
|
|
outstandingCommands: input.snapshot?.state.outstanding.length ?? 0,
|
|
commands: input.commands.flatMap((command) => command.status === "started" && command.activity !== undefined
|
|
? [{ id: command.command, ...command.activity }] : []),
|
|
operations: input.operations.map((operation) => ({
|
|
id: operation.id,
|
|
endpoint: operation.endpoint,
|
|
...(operation.receipt === undefined ? {} : { receipt: operation.receipt }),
|
|
...(operation.wakeAt === undefined ? {} : { wakeAt: operation.wakeAt }),
|
|
status: operation.status,
|
|
...(operation.progress === undefined ? {} : { progress: operation.progress }),
|
|
...(operation.failure === undefined ? {} : { failure: operation.failure }),
|
|
})),
|
|
};
|
|
}
|
|
|
|
/** Active Runtime control that never opens or depends on a ResourceStore. */
|
|
export function createLocalRuntimeControl(
|
|
options: CreateLocalRuntimeControlOptions,
|
|
): LocalRuntimeControl {
|
|
const buildCatalog = options.buildCatalog;
|
|
const inspect = async (build: string): Promise<BuildView | undefined> => {
|
|
const [snapshot, catalog, operations, submission, execution, commands] = await Promise.all([
|
|
options.buildStore.read(build),
|
|
buildCatalog?.read(build),
|
|
options.operationStore.list({ build }),
|
|
options.submissionStore.read(build),
|
|
options.executionStore.read(build),
|
|
options.commandExecutionStore.list(build),
|
|
]);
|
|
return buildView({
|
|
build,
|
|
...(snapshot === undefined ? {} : { snapshot }),
|
|
...(catalog === undefined ? {} : { catalog }),
|
|
...(submission === undefined ? {} : { submission }),
|
|
...(execution === undefined ? {} : { execution }),
|
|
operations,
|
|
commands,
|
|
});
|
|
};
|
|
return {
|
|
inspect,
|
|
async logs(build, lines) {
|
|
return await options.executionLogs?.read(build, lines);
|
|
},
|
|
async activity() {
|
|
const [submissions, executions, capacity] = await Promise.all([
|
|
options.submissionStore.list(),
|
|
options.executionStore.list(),
|
|
options.executionStore.listCapacity(),
|
|
]);
|
|
return {
|
|
builds: (await Promise.all([...submissions, ...executions].map(async (item) =>
|
|
await inspect(item.build))))
|
|
.filter((item): item is BuildView => item !== undefined)
|
|
.sort((left, right) => right.id.localeCompare(left.id)),
|
|
capacity,
|
|
};
|
|
},
|
|
async cancel(build, reason) {
|
|
if (await options.executionStore.read(build) === undefined) return undefined;
|
|
await options.executionStore.requestStop(build, { cause: "user-cancelled",
|
|
...(reason === undefined ? {} : { reason }) });
|
|
return await inspect(build);
|
|
},
|
|
close() {
|
|
return options.close?.();
|
|
},
|
|
};
|
|
}
|