import type { GpuDevice } from "@aicommander/protocol"; /** * What the probe LEARNED, as opposed to what goes on the wire. * * `probeGpus()` collapses "this machine has no NVIDIA card" and "the probe did * not work" into one `undefined`, because the wire format wants exactly that * collapse (see AgentRegisterMsg.gpus). Local consumers need them apart: the job * GPU check may only refuse a `gpuIndex` on a machine it KNOWS is GPU-less, and * must stay permissive when the probe merely hiccuped. * * THREE states, not two, and the third is the point of the type: * - `devices` — the probe RAN, exited cleanly, and EVERY row parsed. The list * is authoritative and non-empty. * - `none` — the probe RAN, exited cleanly, and reported zero devices. Only a * successful execution may produce this, so it is a real claim about the * hardware ("there are no NVIDIA GPUs here"), not an absence of information. * - `unknown` — we learned nothing; every consumer must fall back to whatever * it does without hardware knowledge. * * Three variants rather than one `known` carrying a possibly-empty array, * because the two confident answers have different consequences (one enumerates * cards in a refusal, one refuses outright) and an `if (devices.length === 0)` * inside a `known` branch is exactly the kind of implicit second discriminant * that let a FAILED probe be read as "no GPUs" once already. */ export type GpuProbeState = { certainty: "devices"; devices: GpuDevice[]; } | { certainty: "none"; } | { certainty: "unknown"; }; /** * The probe state as a LOCAL consumer wants it: the cards we know about, or * `undefined` when we know nothing. `[]` here is the confident "no cards", which * is why this reduction and `wireGpus()` are deliberately NOT the same function — * on the wire `[]` is forbidden, locally it is the whole signal. */ export declare function knownGpus(state: GpuProbeState): readonly GpuDevice[] | undefined; /** * Probe the local NVIDIA devices. * * Returns `undefined` — meaning "unknown or none" — when the binary is missing * (the normal case on macOS and every GPU-less Linux box), the process exits * non-zero, the probe times out, the output overflows, or the output does not * parse in full. Callers must OMIT the `gpus` field entirely in that case; an empty * array would claim the same thing less clearly (see AgentRegisterMsg.gpus). * * Kept as a thin wrapper over `probeGpuState()` precisely so the wire contract * has ONE shape and cannot drift: everything richer is a local-only concern. * connection.ts calls `probeGpuState()` and reduces with `wireGpus()` instead — * it needs BOTH answers from ONE probe rather than a second spawn — so this is * the entry point for anything that only cares what goes on the wire. * * No platform branch: `nvidia-smi.exe` is on PATH wherever the Windows driver is * installed, and macOS simply ENOENTs. */ export declare function probeGpus(): Promise; /** * The probe state as the WIRE wants it: devices, or nothing at all. * * The "never send `[]`" rule of AgentRegisterMsg.gpus lives here and only here, * so a confident "no GPUs" cannot leak onto a register frame through some second * caller reducing the state its own way. */ export declare function wireGpus(state: GpuProbeState): GpuDevice[] | undefined; /** * Probe, and say how much to trust the answer. * * EXACTLY ONE path produces a CONFIDENT "none": the tool RAN, exited 0, and * printed nothing. That is what a working driver on a machine with zero visible * devices does, and it is the only observation that actually distinguishes "no * cards" from "we could not look". * * EVERYTHING else is `unknown`, including — deliberately — the spawn ENOENT. * An earlier revision read "nvidia-smi is not on PATH" as "there is no NVIDIA * driver, hence no usable card". That inference is wrong often enough to matter: * this agent is started by launchd/systemd/a service wrapper with a minimal * PATH (/usr/bin:/bin:/usr/sbin:/sbin on macOS), so the binary can be installed * and working — and libcuda perfectly able to bind a device — while THIS process * cannot see it. Treating that as "confidently GPU-less" made every such box * answer a `gpuIndex` with a hard `invalid_request` refusal, killing GPU jobs * that used to run fine. The reclassification knowingly TRADES COVERAGE FOR * HONESTY: the GPU-less-machine refusal now only fires where `nvidia-smi` exists, is * reachable, and reports zero cards. That is a narrower guarantee — and the only * one we can actually stand behind. Everywhere else we stay permissive, exactly * as before this feature existed. * * Also `unknown`: a non-zero exit, a timeout, stdout overflow, a stdout stream * error, a synchronous spawn throw (EACCES on a non-executable file, …), output * that came back non-empty but produced no parseable row, AND a PARTIAL parse * where some rows parsed and at least one did not. The last one is issue 14: an * incomplete list that still claimed to be authoritative would reject a real * GPU index whose row happened to be the one we could not read. A probe is * authoritative only if we understood ALL of it. */ export declare function probeGpuState(): Promise; /** What a parse produced, and how much of it we failed to understand. */ export type GpuParseResult = { devices: GpuDevice[]; /** Non-blank rows we could not read. Any of these makes the probe `unknown`. */ malformed: number; }; /** * Parse `nvidia-smi --format=csv,noheader,nounits` rows. * * Defensive by construction: every row that does not have the exact shape we * asked for is SKIPPED rather than coerced. `[N/A]` — which nvidia-smi prints * for unsupported queries, notably utilization on some laptop/vGPU parts — * coerces to NaN and drops the row with everything else that is not a finite * number. * * But a skipped row is COUNTED, not silently forgotten: the caller needs to know * that the list it got is partial, because "these are the cards" and "these are * the cards whose rows I happened to understand" are different claims, and only * the first may be used to refuse a gpuIndex (issue 14). * * Exported for tests; `probeGpuState` is the only production caller. */ export declare function parseGpuRows(stdout: string): GpuParseResult;