UNPKG

sesterce-cli

Version:

A powerful command-line interface tool for managing Sesterce Cloud services. Sesterce CLI provides easy access to GPU cloud instances, AI inference services, container registries, and SSH key management directly from your terminal.

350 lines (301 loc) 10.4 kB
import { InferenceModel } from "@/modules/ai-inference/domain/inference-model"; import { launchInferenceInstance } from "@/modules/ai-inference/use-cases/launch-instance"; import { listInferenceHardwares } from "@/modules/ai-inference/use-cases/list-inference-hardwares"; import { listInferenceModels } from "@/modules/ai-inference/use-cases/list-inference-models"; import { listInferenceRegions } from "@/modules/ai-inference/use-cases/list-inference-regions"; import { previewInstancePricing } from "@/modules/ai-inference/use-cases/preview-instance-pricing"; import { InstancePricing } from "@/modules/ai-inference/use-cases/preview-instance-pricing/preview-instance-pricing-dto"; import { Registry } from "@/modules/registries/domain/registry"; import { listRegistries } from "@/modules/registries/use-cases/list-registries"; import { checkbox, confirm, editor, input, number, search, } from "@inquirer/prompts"; import type { Command } from "commander"; const printRegistry = (registry: Registry) => { return `${registry.name} - ${registry.url} - ${registry.username}`; }; export function createAIInferenceInstanceLaunchCommand( aiInferenceInstanceCommand: Command ) { aiInferenceInstanceCommand .command("launch") .description("Launch an AI inference instance") .action(async () => { console.log("Loading offers..."); const [hardwaresResult, regionsResult] = await Promise.all([ listInferenceHardwares.execute(), listInferenceRegions.execute(), ]); if (hardwaresResult.isLeft()) { console.error(hardwaresResult.value?.message); return; } if (regionsResult.isLeft()) { console.error(regionsResult.value?.message); return; } const hardwares = hardwaresResult.value; const regions = regionsResult.value; const answer = await checkbox({ message: "Deploy public or private model?", choices: [ { name: "Public model", value: "public", checked: true }, { name: "Private model (registry)", value: "private" }, ], }); const modelType = answer[0]; let model: InferenceModel | null = null; let registry: Registry | null = null; if (modelType === "public") { console.log("Loading public models..."); const modelsResult = await listInferenceModels.execute(); if (modelsResult.isLeft()) { console.error(modelsResult.value.message); return; } const models = modelsResult.value; const selectedModel = await search({ message: "Select a model", source: async (input, { signal }) => { if (!input) return models.map((model) => ({ name: model.name, value: model, })); return models .filter((model) => model.name.toLowerCase().includes(input.toLowerCase()) ) .map((model) => ({ name: model.name, value: model, })); }, }); model = selectedModel; } else { console.log("Loading registries..."); const registriesResult = await listRegistries.execute(); if (registriesResult.isLeft()) { console.error(registriesResult.value.message); return; } const registries = registriesResult.value; const selectedRegistry = await search({ message: "Select a registry", source: async (input, { signal }) => { if (!input) return registries.map((registry) => ({ name: printRegistry(registry), value: registry, })); return registries .filter((registry) => registry.name.toLowerCase().includes(input.toLowerCase()) ) .map((registry) => ({ name: printRegistry(registry), value: registry, })); }, }); registry = selectedRegistry; } const containerPort = await number({ message: "Port to expose the model", required: true, default: model?.port ?? 80, }); const availableHardwares = hardwares.map((hardware) => ({ ...hardware, available: regions.some((region) => region.capacities.some( (capacity) => capacity.hardwareName === hardware.name && capacity.capacity > 0 ) ), })); const hardware = await search({ message: "Select a hardware", source: async (input, { signal }) => { if (!input) return availableHardwares.map((hardware) => ({ name: hardware.name, value: hardware, disabled: !hardware.available ? "(Out of stock)" : false, })); return availableHardwares .filter((hardware) => hardware.name.toLowerCase().includes(input.toLowerCase()) ) .map((hardware) => ({ name: hardware.name, value: hardware, disabled: !hardware.available ? "(Out of stock)" : false, })); }, }); console.log("Loading regions..."); const hardwareRegions = regions.filter((region) => region.capacities.some( (capacity) => capacity.hardwareName === hardware.name && capacity.capacity > 0 ) ); const pricingResult = await previewInstancePricing.execute({ hardwareName: hardware.name, regions: hardwareRegions.map((region) => region.id), maxContainers: 1, }); if (pricingResult.isLeft()) { console.error(pricingResult.value.message); return; } const regionPricingDict = pricingResult.value.pricesPerRegion.reduce( (acc, { regionId, ...pricing }) => { acc[regionId] = pricing; return acc; }, {} as Record<number, InstancePricing> ); const selectedRegions = await checkbox({ message: "Select one or more regions", required: true, choices: hardwareRegions.map((region) => ({ name: regionPricingDict[region.id] ? `${region.name} - $${regionPricingDict[region.id].pricePerHour}/hour` : region.name, value: region, })), }); const startupCommand = await input({ message: "Startup command", }); const minContainers = await number({ message: "Minimum number of containers (0-3)", default: 1, validate: (value) => { if (value === undefined) { return true; } if (value < 0 || value > 3) { return "Minimum number of containers must be between 0 and 3"; } return true; }, }); const maxContainers = await number({ message: "Maximum number of containers (1-25)", required: true, default: 1, validate: (value) => { if (value === undefined) { return true; } if (value < 1 || value > 25) { return "Maximum number of containers must be between 1 and 25"; } return true; }, }); const cooldownPeriod = await number({ message: "Cooldown period (in seconds)", }); const timeout = await number({ message: "Timeout (in seconds)", }); console.log("Container Deployment Triggers"); const cpu = await number({ message: "CPU usage (%)", }); const gpuUtilization = await number({ message: "GPU usage (%)", }); const gpuMemory = await number({ message: "GPU memory (%)", }); const memory = await number({ message: "RAM usage (%)", }); const http = await number({ message: "HTTP requests (/sec)", }); const envVars = await editor({ message: "Environment Variables", }); let envs: Record<string, string> = {}; if (envVars.length > 0) { envs = envVars.split("\n").reduce( (acc, env) => { const [key, value] = env.split("="); if (key && value) { acc[key] = value; } return acc; }, {} as Record<string, string> ); } const name = await input({ message: "Instance name", default: "my-ai-inference-instance", required: true, }); console.log("Calculating final price..."); const finalPrice = await previewInstancePricing.execute({ hardwareName: hardware.name, regions: selectedRegions.map((region) => region.id), maxContainers, }); if (finalPrice.isLeft()) { console.error(finalPrice.value.message); return; } console.log( `Final price: $${finalPrice.value.totalPrice.pricePerHour}/hour | $${finalPrice.value.totalPrice.pricePerMonth}/month` ); const confirmDeployment = await confirm({ message: "Are you sure you want to deploy this instance?", }); if (!confirmDeployment) { console.log("Deployment cancelled"); return; } console.log("Deploying instance..."); const launchInstanceResult = await launchInferenceInstance.execute({ modelId: model?.id ?? null, registryId: registry?._id ?? null, containerPort, name, regionsIds: selectedRegions.map((region) => region.id), minContainers: minContainers ?? 1, maxContainers, cooldownPeriod, triggers: { cpu, gpuUtilization, gpuMemory, memory, http, }, timeout, envs, hardwareName: hardware.name, startupCommand: startupCommand ?? null, }); if (launchInstanceResult.isLeft()) { console.error(launchInstanceResult.value.message); return; } const instance = launchInstanceResult.value; console.log( "Instance launched successfully! Instance ID: ", instance._id ); }); }