/** * Provider capability manifests. * * The extension does NOT integrate with every GPU vendor. It holds a small * capability table per provider — how to check auth, provision, destroy, list — * expressed as command templates with structured output. Providers whose CLIs * are agent-friendly (vastai, runpodctl) need no code changes to be operated. * Providers without a usable CLI (Lambda) use their REST API when an API key is * present. Modal is serverless: there is nothing to provision — deployments are * code, so the adapter only handles auth and defers to the modal skill. * * All commands run via execFile (no shell interpolation of user input). */ import { execFile } from "node:child_process"; export interface CommandSpec { cmd: string; args: (vars: Record) => string[]; } export interface ProviderManifest { id: string; name: string; kind: "instance" | "serverless" | "api"; cli?: string; installHint: string; authCheck?: CommandSpec; provision?: CommandSpec; // returns instance id via parseProvision destroy?: CommandSpec; list?: CommandSpec; notes: string; } export const PROVIDERS: Record = { runpod: { id: "runpod", name: "RunPod", kind: "instance", cli: "runpodctl", installHint: "curl -sSL https://github.com/runpod/runpodctl/releases/latest/download/runpodctl-linux-amd64 -o /usr/local/bin/runpodctl && chmod +x /usr/local/bin/runpodctl", authCheck: { cmd: "runpodctl", args: () => ["get", "pods", "--output", "json"] }, provision: { cmd: "runpodctl", args: (v) => ["create", "pod", "--name", v.name || "vf-pod", "--gpuType", v.gpu, "--template", v.template || "comfyui", "--cloudType", v.cloud || "COMMUNITY", "--containerDiskInGb", v.disk || "20", "--volumeInGb", v.volume || "50"].filter(Boolean), }, destroy: { cmd: "runpodctl", args: (v) => ["remove", "pod", v.id] }, list: { cmd: "runpodctl", args: () => ["get", "pods", "--output", "json"] }, notes: "Community vs Secure pool. Prefer the API flow in skills/provider-adapters/runpod.md (create pod with templateId, poll status until RUNNING).", }, vast: { id: "vast", name: "Vast.ai", kind: "instance", cli: "vastai", installHint: "pip install -U vastai", authCheck: { cmd: "vastai", args: () => ["show", "instances"] }, provision: { cmd: "vastai", args: (v) => ["create", "instance", v.bundle_id, "--disk", v.disk || "30", "--image", v.image || "nvidia/cuda:12.4.1-cudnn-devel-ubuntu22.04", "--ssh", "--direct", "--onstart-cmd", v.onstart || "echo ready"], }, destroy: { cmd: "vastai", args: (v) => ["destroy", "instance", v.id] }, list: { cmd: "vastai", args: () => ["show", "instances", "--raw"] }, notes: "Marketplace — always search offers first (see skills/provider-adapters/vast.md). Cheapest ask wins; watch storage and interruptible pricing.", }, modal: { id: "modal", name: "Modal", kind: "serverless", cli: "modal", installHint: "pip install modal", authCheck: { cmd: "modal", args: () => ["profile", "current"] }, notes: "Serverless: no machines to provision. Deployments are Python apps (modal deploy). Billing only while a container runs. See skills/provider-adapters/modal.md.", }, lambda: { id: "lambda", name: "Lambda", kind: "api", installHint: "No CLI — API key from https://cloud.lambdalabs.com/api-keys (env LAMBDA_API_KEY)", authCheck: { cmd: "bash", args: () => ["-c", "test -n \"$LAMBDA_API_KEY\" && echo ok || echo missing"] }, provision: { cmd: "bash", args: (v) => ["-c", `curl -s -X POST https://api.lambdalabs.com/v1/instances -H "Authorization: Bearer $LAMBDA_API_KEY" -H "Content-Type: application/json" -d '{"instance_type_name":"${v.instance_type}","region_name":"${v.region || "us-east-1"}","ssh_key_names":["${v.ssh_key || ""}"]}'`], }, destroy: { cmd: "bash", args: (v) => ["-c", `curl -s -X DELETE https://api.lambdalabs.com/v1/instances/${v.id} -H "Authorization: Bearer $LAMBDA_API_KEY"`] }, list: { cmd: "bash", args: () => ["-c", `curl -s https://api.lambdalabs.com/v1/instances -H "Authorization: Bearer $LAMBDA_API_KEY"`] }, notes: "Instance type names from GET /v1/instance-types (e.g. gpu_1x_h100_sxm). Hourly billing, no egress fees.", }, prime: { id: "prime", name: "Prime Intellect", kind: "instance", cli: "prime", installHint: "pip install prime (verify current CLI name at docs.primeintellect.ai)", authCheck: { cmd: "prime", args: () => ["--help"] }, notes: "Decentralized marketplace — dynamic per-cluster pricing, spot available. CLI surface changes; verify commands at deploy time (skills/provider-adapters/prime.md).", }, salad: { id: "salad", name: "SaladCloud", kind: "api", installHint: "No CLI — API key from https://portal.salad.com (env SALAD_API_KEY + SALAD_ORG + SALAD_PROJECT)", authCheck: { cmd: "bash", args: () => ["-c", "test -n \"$SALAD_API_KEY\" && echo ok || echo missing"] }, provision: { cmd: "bash", args: (v) => ["-c", `curl -s -X POST https://api.salad.com/api/public/organizations/$SALAD_ORG/projects/$SALAD_PROJECT/containers -H "Salad-Api-Key: $SALAD_API_KEY" -H "Content-Type: application/json" -d '{"name":"${v.name || "vf-container"}","container_groups":[{"name":"${v.name || "vf-container"}","container":{"image":"${v.image || "nvidia/cuda:12.4.1-cudnn-devel-ubuntu22.04"}","command":[]},"resources":{"gpu_classes":["${v.gpu || "rtx_5090"}"],"gpus":1}}]'`], }, destroy: { cmd: "bash", args: (v) => ["-c", `curl -s -X DELETE https://api.salad.com/api/public/organizations/$SALAD_ORG/projects/$SALAD_PROJECT/containers/${v.id} -H "Salad-Api-Key: $SALAD_API_KEY"`] }, list: { cmd: "bash", args: () => ["-c", `curl -s https://api.salad.com/api/public/organizations/$SALAD_ORG/projects/$SALAD_PROJECT/containers -H "Salad-Api-Key: $SALAD_API_KEY"`] }, notes: "Distributed consumer-GPU fleet; batch pricing is the cheapest lane (5090 ~$0.294/hr). Container-based: deploy a Dockerized worker, not a pod. Availability/startup vary — treat as the aggressive lane for retryable batches (docs/providers/salad.md).", }, }; export interface ExecResult { ok: boolean; stdout: string; stderr: string; error?: string; } export function runSpec(spec: CommandSpec, vars: Record, timeoutMs = 60000): Promise { return new Promise((resolve) => { execFile(spec.cmd, spec.args(vars), { timeout: timeoutMs, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => { if (err) { resolve({ ok: false, stdout: String(stdout || ""), stderr: String(stderr || ""), error: (err as Error).message }); } else { resolve({ ok: true, stdout: String(stdout || ""), stderr: String(stderr || "") }); } }); }); } export function cliExists(bin: string): Promise { return new Promise((resolve) => { execFile("sh", ["-c", `command -v ${bin} >/dev/null 2>&1 && ${bin} --version 2>/dev/null | head -1 || true`], { timeout: 10000 }, (err, stdout) => { resolve(!err && stdout.trim().length > 0); }); }); } /** Best-effort instance id extraction from a provider's provision output. */ export function parseInstanceId(provider: string, stdout: string): string | null { try { const j = JSON.parse(stdout); const candidates = [j.id, j.pod?.id, j.instance?.id, j.data?.id, j.instance_id]; for (const c of candidates) if (typeof c === "string" || typeof c === "number") return String(c); if (Array.isArray(j)) return String(j[0]?.id ?? ""); return null; } catch { const m = stdout.match(/(?:pod|instance)[_-]?id["':=\s]+([a-zA-Z0-9_-]{6,})/i); return m ? m[1] : null; } }