README.md

vLLM Studio Controller

TypeScript backend for vLLM Studio, built on Bun + Hono + SQLite. Manages model lifecycle (launch/evict/preempt) across vLLM, SGLang, llama.cpp, and TabbyAPI backends, with real-time monitoring, agent runtime, and OpenAI-compatible API proxying.

Quick Start

# Install dependencies
bun install

# Run (development with auto-reload)
bun --watch src/main.ts

# Run (production)
bun src/main.ts

# Type check
npx tsc --noEmit

# Lint + format
bun run lint:fix && bun run format

# Tests
bun test

Important: Use native bun (~/.bun/bin/bun), not snap bun. Snap bun has sandbox restrictions that block nvidia-smi, breaking GPU monitoring. Use ./start.sh from the project root which handles this automatically.

Architecture

main.ts                    Entry point, Bun.serve(), shutdown handlers
  └── app-context.ts       Dependency injection container (AppContext)
        ├── config/env.ts   Environment config (.env cascade)
        ├── core/           Logger, errors, AsyncLock, log files
        ├── http/app.ts     Hono app, CORS, route registration
        ├── routes/         HTTP endpoints (16 route modules)
        ├── services/       Business logic (process mgmt, backends, GPU, metrics, agent runtime)
        └── stores/         SQLite persistence (recipes, chats, MCP, metrics, downloads)

Request Flow

HTTP Request → Hono middleware (CORS, auth, error handling)
  → Route handler (routes/*.ts)
    → Service layer (services/*.ts)
      → Store layer (stores/*.ts) → SQLite
      → Process manager → spawn/kill inference processes
      → Event manager → SSE broadcast to connected clients

Dependency Injection

All components receive AppContext, created once in app-context.ts:

interface AppContext {
  config: RuntimeConfig;
  logger: Logger;
  eventManager: EventManager;
  launchState: LaunchState;
  metrics: ControllerMetrics;
  metricsRegistry: MetricsRegistry;
  processManager: ProcessManager;
  downloadManager: DownloadManager;
  runManager: ChatRunManager;
  stores: {
    recipeStore: RecipeStore;
    chatStore: ChatStore;
    downloadStore: DownloadStore;
    peakMetricsStore: PeakMetricsStore;
    lifetimeMetricsStore: LifetimeMetricsStore;
    mcpStore: McpStore;
  };
}

Route Registration

All routes follow the same pattern and are wired in http/app.ts:

registerSystemRoutes(app, context);
registerLifecycleRoutes(app, context);
registerMonitoringRoutes(app, context);
// ...etc

Source Structure

src/
├── main.ts                  Server startup, nvidia-smi check, shutdown
├── app-context.ts           DI container creation
├── metrics-collector.ts     Background metrics loop (5s interval)
│
├── config/
│   ├── env.ts               RuntimeConfig, .env loading with cascade
│   └── persisted-config.ts  Persistent settings (SQLite-backed)
│
├── core/
│   ├── logger.ts            Log levels, disk + SSE output
│   ├── errors.ts            HttpStatus class (notFound, badRequest, etc.)
│   ├── async.ts             AsyncLock (mutex with timeout/cancellation), AsyncQueue, delay
│   └── log-files.ts         Log rotation, cleanup, path helpers
│
├── types/
│   ├── models.ts            Backend, Recipe, ProcessInfo, LaunchResult, GpuInfo
│   ├── context.ts           AppContext interface
│   ├── schemas.ts           Zod validation schemas
│   └── brand.ts             Branded types
│
├── http/
│   ├── app.ts               Hono app setup, middleware, route registration
│   └── sse.ts               SSE streaming helpers
│
├── routes/
│   ├── system.ts            /health, /status, /gpus, /config
│   ├── lifecycle.ts         /recipes CRUD, /launch/{id}, /evict, /wait-ready
│   ├── models.ts            /v1/models (OpenAI format), model discovery
│   ├── chats.ts             /chats CRUD, messages, forking
│   ├── openai.ts            OpenAI-compatible chat completions proxy
│   ├── logs.ts              /logs list/stream, /events SSE
│   ├── monitoring.ts        /metrics (Prometheus), /peak-metrics, /benchmark
│   ├── usage.ts             /usage analytics (Postgres-backed)
│   ├── mcp.ts               /mcp/servers, /mcp/tools
│   ├── downloads.ts         Model download endpoints
│   ├── runtime.ts           Runtime info (versions, paths)
│   ├── studio.ts            Studio-specific endpoints
│   ├── tokenization.ts      /tokenize endpoint
│   └── agent-files.ts       Agent filesystem operations
│
├── services/
│   ├── process-manager.ts   Process spawn, findInferenceProcess, eviction
│   ├── backends.ts          Command builders for vLLM, SGLang, llama.cpp, TabbyAPI
│   ├── event-manager.ts     SSE publish/subscribe with channels
│   ├── gpu.ts               nvidia-smi wrapper, memory estimation
│   ├── metrics.ts           Prometheus registry (prom-client)
│   ├── model-browser.ts     Local model directory scanning
│   ├── download-manager.ts  HuggingFace model downloads
│   ├── launch-state.ts      Launch state tracking (mutex, abort)
│   ├── llamacpp-runtime.ts  llama.cpp-specific runtime handling
│   ├── vllm-runtime.ts      vLLM-specific runtime handling
│   ├── runtime-info.ts      System/runtime info gathering
│   ├── chat-compaction.ts   Chat message compaction
│   ├── tool-call-core.ts    Tool call parsing/execution
│   ├── proxy-parsers.ts     Response stream parsing
│   ├── process-utilities.ts Process helper functions
│   ├── command/             Command building utilities
│   ├── downloads/           Download helpers (globs, math, paths, HF API)
│   └── agent-runtime/       Agent orchestration system
│       ├── run-manager.ts          Chat run orchestration
│       ├── agent-event-handler.ts  Event handling for agent runs
│       ├── message-mapper.ts       OpenAI <-> Agent message mapping
│       ├── run-manager-persistence.ts  Run persistence
│       ├── run-manager-sse.ts      Run SSE streaming
│       ├── system-prompt-builder.ts  System prompt construction
│       ├── tool-registry.ts        Tool registry interface
│       ├── tool-registry-common.ts Common tools (web search, etc.)
│       ├── tool-registry-agentfs.ts AgentFS integration
│       ├── tool-registry-mcp.ts    MCP tool integration
│       ├── tool-registry-plan.ts   Planning tool
│       ├── stream-openai-completions-safe.ts  Safe streaming
│       └── model-factory.ts        Model instance creation
│
└── stores/
    ├── recipe-store.ts      Recipe CRUD + migrations
    ├── chat-store.ts        Chat sessions/messages
    ├── chat-store-schema.ts Schema definitions
    ├── chat-store-hydration.ts Data hydration
    ├── chat-store-runs.ts   Run persistence
    ├── mcp-store.ts         MCP server configuration
    ├── download-store.ts    Download tracking
    ├── metrics-store.ts     PeakMetricsStore + LifetimeMetricsStore
    └── recipe-serializer.ts Recipe JSON serialization

Key Services

Process Manager (services/process-manager.ts)

Spawns inference backends as detached child processes. Monitors process health via PID liveness checks. Handles eviction with SIGTERM -> SIGKILL escalation.

Backends (services/backends.ts)

Builds CLI commands for each backend. Handles:

  • vLLM: tensor parallelism, quantization, tool/reasoning parser auto-detection
  • SGLang: similar to vLLM with SGLang-specific flags
  • llama.cpp: GGUF model loading, split modes (row/layer/graph), flash attention, KV cache quantization
  • TabbyAPI: ExLlamaV2-based serving

Launch Lifecycle (routes/lifecycle.ts)

State machine for model launches with:

  • Preemption: launching a new model auto-evicts the current one
  • Cancellation: abort via /evict during launch
  • PID liveness: detects dead processes within one poll cycle (~2s)
  • Fatal pattern detection: catches argparse errors, import failures, tracebacks
  • Progress events: SSE updates during model loading
  • AsyncLock: serialized access prevents concurrent launches

Metrics Collector (metrics-collector.ts)

Background loop (5s interval) that:

  • Scrapes inference backend /metrics endpoint (vLLM Prometheus metrics)
  • Calculates token throughput from deltas
  • Tracks GPU power consumption and energy (watt-hours)
  • Updates peak metrics (best observed TPS per model)
  • Publishes real-time SSE events (status, gpu, metrics)

Agent Runtime (services/agent-runtime/)

Multi-turn agent system with:

  • Tool calling (MCP servers, AgentFS, web search, planning)
  • Streaming OpenAI completions with safe parsing
  • System prompt construction with context
  • Run persistence and resumption

Databases

Two SQLite databases (via Bun's built-in driver):

Database Tables Purpose
controller.db recipes, mcp_servers, peak_metrics, lifetime_metrics, downloads Core app state
chats.db chat_sessions, chat_messages, chat_runs Chat persistence

Monitoring

Prometheus Metrics (prefix: vllm_studio_)

Metric Type Description
inference_server_up Gauge 1 if inference is running
gpu_memory_used_bytes Gauge Per-GPU memory usage
gpu_memory_total_bytes Gauge Per-GPU total memory
gpu_utilization_percent Gauge Per-GPU compute utilization
gpu_temperature_celsius Gauge Per-GPU temperature
model_switches_total Counter Total model switches
model_launch_failures_total Counter Failed launches
model_switch_duration_seconds Histogram Switch latency
sse_active_connections Gauge Active SSE clients
sse_events_published_total Counter Total SSE events

SSE Events (GET /events)

Event Type Data Interval
status Process info, inference readiness 5s
gpu GPU list with memory/util/temp/power 5s
metrics Throughput, tokens, power, energy 5s
launch_progress Stage, percentage, message During launch
download_progress Download status, bytes, speed During download

Configuration

Environment variables (all prefixed with VLLM_STUDIO_):

Variable Default Description
PORT 8080 Controller HTTP port
HOST 0.0.0.0 Bind address
INFERENCE_PORT 8000 Inference backend port
API_KEY (none) Optional auth key
DATA_DIR ./data Data directory
DB_PATH ./data/controller.db SQLite path
MODELS_DIR /models Model weights directory
SGLANG_PYTHON (auto) Python path for SGLang
TABBY_API_DIR (none) TabbyAPI installation
NVIDIA_SMI_PATH nvidia-smi nvidia-smi binary path

Development

# Watch mode (auto-reload on changes)
bun --watch src/main.ts

# Type check
npx tsc --noEmit

# Lint
bun run lint

# Format
bun run format

# Run tests
bun test

# Check for unused exports and duplicate code
bun run check

Adding a New Route

  1. Create src/routes/my-feature.ts
  2. Export registerMyFeatureRoutes(app: Hono, context: AppContext)
  3. Register in src/http/app.ts

Adding a New Store

  1. Create src/stores/my-store.ts extending the SQLite pattern
  2. Initialize in app-context.ts
  3. Add to AppContext.stores in types/context.ts