/** * video-factory registry — the persistent state store for GPU infrastructure. * * Everything the agent does with GPUs is recorded here so a Pi session can die, * restart, and still know exactly what is deployed, what ran, and what it cost. * * Layout (all under /.pi/gpu/, override with GPU_STATE_DIR): * providers.json — provider CLIs detected + authenticated (from /gpu setup) * machines.json — raw provisioned instances * deployments.json — the stable deployment abstraction (machine + runtime + model) * models.json — model manifests (VRAM, source, runtime, gpu class) * workflows.json — executable workflow registry * jobs.jsonl — append-only job ledger (the source of truth for cost) * policy.json — spend ceilings + confirmation thresholds */ import { mkdirSync, readFileSync, writeFileSync, existsSync, appendFileSync } from "node:fs"; import * as path from "node:path"; export function stateDir(): string { return process.env.GPU_STATE_DIR || path.join(process.cwd(), ".pi", "gpu"); } function file(name: string): string { return path.join(stateDir(), name); } function read(name: string, fallback: T): T { try { if (!existsSync(file(name))) return fallback; return JSON.parse(readFileSync(file(name), "utf-8")) as T; } catch { return fallback; } } function write(name: string, data: unknown): void { mkdirSync(stateDir(), { recursive: true }); writeFileSync(file(name), JSON.stringify(data, null, 2) + "\n"); } // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface ProviderState { id: string; name: string; kind: "instance" | "serverless" | "api"; cli?: string; installed: boolean; cliVersion?: string; authenticated: boolean; authHint?: string; detectedAt?: string; } export interface Machine { id: string; provider: string; instance_id: string; gpu: string; gpu_count: number; status: "running" | "stopped" | "error" | "destroyed"; cost_hr: number; region?: string; created_at: string; tags: string[]; } export type DeploymentStatus = "provisioning" | "ready" | "stopped" | "error" | "destroyed"; export interface Deployment { id: string; provider: string; machine_id?: string; instance_id?: string; gpu: string; gpu_count: number; runtime: string; model?: string; endpoint?: string; ssh?: string; status: DeploymentStatus; cost_hr: number; region?: string; created_at: string; idle_since?: string | null; } export interface ModelManifest { id: string; name: string; source_url: string; params_b?: number; vram_gb_min: number; vram_gb_fp8?: number; gpu_class: string; runtime: string; notes: string; verified: boolean; } export interface WorkflowManifest { id: string; model?: string; runtime: "comfyui" | "http" | "provider-api" | "native"; description: string; input_schema: Record; verified: boolean; } export type JobStatus = "queued" | "running" | "completed" | "failed" | "cancelled"; export interface Job { id: string; deployment_id?: string; provider?: string; model?: string; workflow: string; workflow_version?: number; inputs: Record; status: JobStatus; started_at: string; completed_at?: string; duration_s?: number; cost_usd?: number; gpu?: string; instance_id?: string; artifacts: { name: string; uri: string }[]; error?: string; ts: string; } export interface SpendPolicy { ceiling_per_job_usd: number; ceiling_daily_usd: number; ceiling_monthly_usd: number; confirm_above_usd: number; idle_shutdown_after_min: number; } // --------------------------------------------------------------------------- // Loaders / writers // --------------------------------------------------------------------------- export function loadProviders(): ProviderState[] { return read("providers.json", []); } export function saveProviders(p: ProviderState[]): void { write("providers.json", p); } export function loadMachines(): Machine[] { return read("machines.json", []); } export function saveMachines(m: Machine[]): void { write("machines.json", m); } export function loadDeployments(): Deployment[] { return read("deployments.json", []); } export function saveDeployments(d: Deployment[]): void { write("deployments.json", d); } export function loadModels(): ModelManifest[] { const seeded: ModelManifest[] = [ { id: "minimax-h3", name: "MiniMax H3 (Hailuo)", source_url: "https://github.com/minimax-ai/Hailuo-H3", vram_gb_min: 24, vram_gb_fp8: 24, gpu_class: "24GB+ quantized (RTX 4090/5090, PRO 4500/6000); 48GB+ for fp16", runtime: "comfyui", notes: "15s/2K native w/ stereo audio; 1 finished minute = 4x15s clips. Use Turbo LoRA 4-8 step + SageAttention2 + INT8 — never the vanilla 20-step workflow (4-10x slower). Anchors: RTX 6000 480p/15s = 56s @4-step, 1m47s @8-step; 5090 15s/0.4MP ≈ 3min. See docs/h3-economics.md.", verified: false, }, { id: "wan-2.2-14b", name: "Wan 2.2 14B (T2V)", source_url: "https://huggingface.co/Wan-AI/Wan2.2-T2V-14B", vram_gb_min: 40, vram_gb_fp8: 26, gpu_class: "48GB+ or 24GB with fp8/offload", runtime: "comfyui", notes: "Native ComfyUI nodes. Also I2V variant.", verified: false, }, { id: "wan-2.2-5b", name: "Wan 2.2 5B (T2V)", source_url: "https://huggingface.co/Wan-AI/Wan2.2-T2V-5B", vram_gb_min: 16, gpu_class: "24GB class (RTX 4090/5090, L40S)", runtime: "comfyui", notes: "Fast cheap pass on 24GB cards.", verified: false, }, { id: "hunyuan-video", name: "HunyuanVideo 13B", source_url: "https://huggingface.co/tencent/HunyuanVideo", vram_gb_min: 60, vram_gb_fp8: 40, gpu_class: "80GB class; 4090 only with aggressive offload", runtime: "comfyui", notes: "Needs ComfyUI-HunyuanVideoWrapper custom node.", verified: false, }, { id: "seedance-1.0-lite", name: "Seedance 1.0 Lite", source_url: "https://huggingface.co/ByteDance-Seed/Seedance-1.0-lite", vram_gb_min: 40, gpu_class: "48GB+", runtime: "comfyui", notes: "Verify open-weight availability + runtime support at deploy time.", verified: false, }, ]; const existing = read("models.json", []); const byId = new Map(existing.map((m) => [m.id, m])); for (const s of seeded) if (!byId.has(s.id)) byId.set(s.id, s); const out = [...byId.values()]; write("models.json", out); return out; } export function loadWorkflows(): WorkflowManifest[] { const seeded: WorkflowManifest[] = [ { id: "h3-text-to-video", model: "minimax-h3", runtime: "comfyui", description: "H3: prompt to video", input_schema: { prompt: "string", duration: "number", width: "number", height: "number" }, verified: false }, { id: "h3-image-to-video", model: "minimax-h3", runtime: "comfyui", description: "H3: image + prompt to video", input_schema: { image: "uri", prompt: "string", duration: "number" }, verified: false }, { id: "wan-t2v", model: "wan-2.2-14b", runtime: "comfyui", description: "Wan 2.2: text to video", input_schema: { prompt: "string", duration: "number" }, verified: false }, { id: "wan-i2v", model: "wan-2.2-14b", runtime: "comfyui", description: "Wan 2.2: image to video", input_schema: { image: "uri", prompt: "string" }, verified: false }, { id: "hunyuan-t2v", model: "hunyuan-video", runtime: "comfyui", description: "HunyuanVideo: text to video", input_schema: { prompt: "string", duration: "number" }, verified: false }, { id: "upscale-2x", runtime: "comfyui", description: "2x upscale video", input_schema: { video: "uri" }, verified: false }, { id: "interpolate", runtime: "comfyui", description: "Frame interpolation", input_schema: { video: "uri", factor: "number" }, verified: false }, { id: "compose-slideshow", runtime: "comfyui", description: "Assemble clips into a slideshow", input_schema: { clips: "array", order: "array" }, verified: false }, ]; const existing = read("workflows.json", []); const byId = new Map(existing.map((w) => [w.id, w])); for (const s of seeded) if (!byId.has(s.id)) byId.set(s.id, s); const out = [...byId.values()]; write("workflows.json", out); return out; } export function loadJobs(): Job[] { try { if (!existsSync(file("jobs.jsonl"))) return []; return readFileSync(file("jobs.jsonl"), "utf-8") .split("\n") .filter(Boolean) .map((l) => JSON.parse(l) as Job); } catch { return []; } } export function appendJob(job: Job): void { mkdirSync(stateDir(), { recursive: true }); appendFileSync(file("jobs.jsonl"), JSON.stringify(job) + "\n"); } export function loadPolicy(): SpendPolicy { const def: SpendPolicy = { ceiling_per_job_usd: 5, ceiling_daily_usd: 40, ceiling_monthly_usd: 400, confirm_above_usd: 1, idle_shutdown_after_min: 30, }; return { ...def, ...read>("policy.json", {}) }; } export function savePolicy(p: SpendPolicy): void { write("policy.json", p); } // --------------------------------------------------------------------------- // Ledger queries // --------------------------------------------------------------------------- export function jobsWhere(f: (j: Job) => boolean): Job[] { return loadJobs().filter(f); } export function monthSpendUsd(): number { const now = new Date(); const start = new Date(now.getFullYear(), now.getMonth(), 1).toISOString(); return jobsWhere((j) => j.ts >= start && j.status === "completed").reduce((s, j) => s + (j.cost_usd || 0), 0); } export function todaySpendUsd(): number { const start = new Date().toISOString().slice(0, 10); return jobsWhere((j) => j.ts.slice(0, 10) === start && j.status === "completed").reduce((s, j) => s + (j.cost_usd || 0), 0); } export function nextJobId(): string { const n = loadJobs().length + 1; return `J-${String(n).padStart(6, "0")}`; } export function nextDeploymentId(model?: string): string { const n = loadDeployments().length + 1; return `dep_${(model || "gpu").replace(/[^a-z0-9]/gi, "").toLowerCase().slice(0, 12)}_${String(n).padStart(2, "0")}`; } export function nextMachineId(): string { const n = loadMachines().length + 1; return `m_${String(n).padStart(3, "0")}`; } export function fmtUsd(n: number): string { return `$${n.toFixed(2)}`; }