import { Effect } from "effect";import type { Config } from "../../config/env";import { runCommandAsyncEffect } from "../../core/command";import type { EventManager } from "../system/event-manager";import type { DeviceId, HostProfile, EngineRuntimeKind } from "./contracts";import { makeTelemetry, profileFrom, type Telemetry } from "./devices/snapshot";import { makeInstanceStore, type InstanceStore } from "./instances/store";import { makeDockerLauncher } from "./launchers/docker";import { makeProcessLauncher } from "./launchers/process";import type { Launcher } from "./launchers/launcher";import { makeComputeService, type ComputeService } from "./lifecycle"; /** * Assembly point: telemetry + store + launchers wired into one ComputeService. This is * the compute layer's entire footprint in AppContext — there is no other state. */ export interface Compute { readonly service: ComputeService; readonly telemetry: Telemetry; readonly store: InstanceStore; readonly host: () => Effect.Effect<HostProfile>;} const DOCKER_PROBE_TIMEOUT_MS = 5_000;/** Docker appearing or losing GPU passthrough mid-run is rare; refresh occasionally. */const DOCKER_PROBE_TTL_MS = 60_000; interface DockerStatus { readonly docker: boolean; readonly dockerGpu: boolean;} const probeDocker = (): Effect.Effect<DockerStatus> => Effect.gen(function* () { const info = yield* runCommandAsyncEffect("docker", ["info", "--format", "{{.Runtimes}}"], { timeoutMs: DOCKER_PROBE_TIMEOUT_MS, }); if (info.status !== 0) return { docker: false, dockerGpu: false }; // The NVIDIA container runtime advertises itself in `docker info`; ROCm needs no // special runtime (plain device mounts), so any working docker on linux counts. const gpuRuntime = info.stdout.includes("nvidia"); return { docker: true, dockerGpu: gpuRuntime || process.platform === "linux" }; }).pipe(Effect.catchCause(() => Effect.succeed({ docker: false, dockerGpu: false }))); export const makeCompute = (config: Config, eventManager: EventManager): Compute => { const telemetry = makeTelemetry({ storagePaths: [config.data_dir, config.models_dir], }); const store = makeInstanceStore(config.data_dir); let dockerCache: { readonly at: number; readonly value: DockerStatus } | null = null; const dockerStatus = (): Effect.Effect<DockerStatus> => Effect.gen(function* () { if (dockerCache && Date.now() - dockerCache.at < DOCKER_PROBE_TTL_MS) { return dockerCache.value; } const value = yield* probeDocker(); dockerCache = { at: Date.now(), value }; return value; }); let lastProfile: HostProfile | null = null; const host = (): Effect.Effect<HostProfile> => Effect.gen(function* () { const snapshot = yield* telemetry.snapshot(); const docker = yield* dockerStatus(); const profile = profileFrom(snapshot, { nodeId: "self", docker: docker.docker, dockerGpu: docker.dockerGpu, }); lastProfile = profile; return profile; }); const processLauncher = makeProcessLauncher(store.logPath); const launcherFor = (runtime: EngineRuntimeKind): Launcher => runtime === "docker" ? makeDockerLauncher(lastProfile?.accelerator ?? "cuda") : processLauncher; const freeDevices = (): Effect.Effect<readonly DeviceId[]> => telemetry .snapshot() .pipe(Effect.map((snapshot) => snapshot.accelerators.map((accelerator) => accelerator.id))); const service = makeComputeService({ store, launcherFor, host, freeDevices, onEvent: (name, stage, message) => eventManager.publishLaunchProgress(name, stage, message), }); return { service, telemetry, store, host };};
import { Effect } from "effect";
import type { Config } from "../../config/env";
import { runCommandAsyncEffect } from "../../core/command";
import type { EventManager } from "../system/event-manager";
import type { DeviceId, HostProfile, EngineRuntimeKind } from "./contracts";
import { makeTelemetry, profileFrom, type Telemetry } from "./devices/snapshot";
import { makeInstanceStore, type InstanceStore } from "./instances/store";
import { makeDockerLauncher } from "./launchers/docker";
import { makeProcessLauncher } from "./launchers/process";
import type { Launcher } from "./launchers/launcher";
import { makeComputeService, type ComputeService } from "./lifecycle";
/**
* Assembly point: telemetry + store + launchers wired into one ComputeService. This is
* the compute layer's entire footprint in AppContext — there is no other state.
*/
export interface Compute {
readonly service: ComputeService;
readonly telemetry: Telemetry;
readonly store: InstanceStore;
readonly host: () => Effect.Effect<HostProfile>;
}
const DOCKER_PROBE_TIMEOUT_MS = 5_000;
/** Docker appearing or losing GPU passthrough mid-run is rare; refresh occasionally. */
const DOCKER_PROBE_TTL_MS = 60_000;
interface DockerStatus {
readonly docker: boolean;
readonly dockerGpu: boolean;
}
const probeDocker = (): Effect.Effect<DockerStatus> =>
Effect.gen(function* () {
const info = yield* runCommandAsyncEffect("docker", ["info", "--format", "{{.Runtimes}}"], {
timeoutMs: DOCKER_PROBE_TIMEOUT_MS,
});
if (info.status !== 0) return { docker: false, dockerGpu: false };
// The NVIDIA container runtime advertises itself in `docker info`; ROCm needs no
// special runtime (plain device mounts), so any working docker on linux counts.
const gpuRuntime = info.stdout.includes("nvidia");
return { docker: true, dockerGpu: gpuRuntime || process.platform === "linux" };
}).pipe(Effect.catchCause(() => Effect.succeed({ docker: false, dockerGpu: false })));
export const makeCompute = (config: Config, eventManager: EventManager): Compute => {
const telemetry = makeTelemetry({
storagePaths: [config.data_dir, config.models_dir],
});
const store = makeInstanceStore(config.data_dir);
let dockerCache: { readonly at: number; readonly value: DockerStatus } | null = null;
const dockerStatus = (): Effect.Effect<DockerStatus> =>
Effect.gen(function* () {
if (dockerCache && Date.now() - dockerCache.at < DOCKER_PROBE_TTL_MS) {
return dockerCache.value;
}
const value = yield* probeDocker();
dockerCache = { at: Date.now(), value };
return value;
});
let lastProfile: HostProfile | null = null;
const host = (): Effect.Effect<HostProfile> =>
Effect.gen(function* () {
const snapshot = yield* telemetry.snapshot();
const docker = yield* dockerStatus();
const profile = profileFrom(snapshot, {
nodeId: "self",
docker: docker.docker,
dockerGpu: docker.dockerGpu,
});
lastProfile = profile;
return profile;
});
const processLauncher = makeProcessLauncher(store.logPath);
const launcherFor = (runtime: EngineRuntimeKind): Launcher =>
runtime === "docker"
? makeDockerLauncher(lastProfile?.accelerator ?? "cuda")
: processLauncher;
const freeDevices = (): Effect.Effect<readonly DeviceId[]> =>
telemetry
.snapshot()
.pipe(Effect.map((snapshot) => snapshot.accelerators.map((accelerator) => accelerator.id)));
const service = makeComputeService({
store,
launcherFor,
host,
freeDevices,
onEvent: (name, stage, message) =>
eventManager.publishLaunchProgress(name, stage, message),
});
return { service, telemetry, store, host };
};