feat(db): Llm.kind discriminator + virtual-provider lifecycle (v1 Stage 1)

First step of the virtual-LLM feature. A virtual Llm row is one that
gets *registered by an mcplocal client* rather than created via
\`mcpctl create llm\`. Its inference is relayed back through an SSE
control channel to the publishing session (mcpd routes added in
Stage 3). The lifecycle fields below let mcpd reap stale rows when
the publisher goes away.

Schema additions:
- enum LlmKind (public | virtual). Default public.
- enum LlmStatus (active | inactive | hibernating). Default active.
  hibernating is reserved for v2 wake-on-demand.
- Llm.kind, providerSessionId, lastHeartbeatAt, status, inactiveSince.
- @@index([kind, status]) for the GC sweep.
- @@index([providerSessionId]) for the reconnect lookup.

All existing rows backfill with kind=public/status=active so v1 is
purely additive — public LLMs ignore the lifecycle columns entirely.

7 new prisma-level assertions in tests/llm-virtual-schema.test.ts
cover: defaults, persisting kind=virtual + lifecycle together, the
active→inactive flip, hibernating value, enum rejection, the
(kind,status) GC index, the providerSessionId reconnect index.

mcpd suite still 801/801 (regenerated client) and typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michal
2026-04-27 13:59:44 +01:00
parent e65a396d3e
commit 1acd8b58bc
3 changed files with 190 additions and 13 deletions

View File

@@ -0,0 +1,16 @@
-- Add Llm.kind/status discriminators and virtual-provider lifecycle fields.
-- Existing rows backfill with kind='public' / status='active' so v1 is purely
-- additive — public LLMs ignore the lifecycle columns entirely.
CREATE TYPE "LlmKind" AS ENUM ('public', 'virtual');
CREATE TYPE "LlmStatus" AS ENUM ('active', 'inactive', 'hibernating');
ALTER TABLE "Llm"
ADD COLUMN "kind" "LlmKind" NOT NULL DEFAULT 'public',
ADD COLUMN "providerSessionId" TEXT,
ADD COLUMN "lastHeartbeatAt" TIMESTAMP(3),
ADD COLUMN "status" "LlmStatus" NOT NULL DEFAULT 'active',
ADD COLUMN "inactiveSince" TIMESTAMP(3);
CREATE INDEX "Llm_kind_status_idx" ON "Llm"("kind", "status");
CREATE INDEX "Llm_providerSessionId_idx" ON "Llm"("providerSessionId");