From dfbf8a2bb6b8af1844ce2e54588bf7874b410ba0 Mon Sep 17 00:00:00 2001 From: Bryandero98 Date: Fri, 4 Sep 2026 02:47:51 -0500 Subject: [PATCH] feat: add a disabled-by-default Slotstream AI Providers preset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #5816. The rest of the issue's scope was already shipped independently (#6162 and follow-ups): slotstreamModelManager.js already enumerates cached checkpoints, previews size/destination/free-disk and refuses before the first byte via the shared download-preflight helper, verifies each file's sha256, and resumes across restart/cancel; the consent modal (DownloadPreflightConfirm.jsx) and card UI (SlotstreamServerCard.jsx) already wire it up. The one piece missing was the AI Providers preset itself. - data.reference/providers.json + providers.sample.json: new `slotstream` api-type preset (127.0.0.1:5564, disabled by default), modeled exactly on the mtplx preset. Text-only, so no CLI/TUI wrapper is registered, unlike MTPLX. - migration 340: seeds the preset into existing installs via the same makeProviderSeedMigration factory migration 272 used for MTPLX. - docs/features/slotstream.md: removes the "if one exists" hedge now that the preset is real, documents the per-file hash verification, and adds an AI Providers section. - docs/features/product-surfaces.md: lists Slotstream (and MTPLX, previously missing) under Local LLM Runtimes. - Two existing tests hardcode the shipped-provider-id list this preset joins (an api-type provider always gets a refresh button): client/src/utils/providers.test.js and server/lib/aiToolkit/internal/modelFetchers.test.js, both updated to include 'slotstream' in their expected/refreshable lists. Verified: the new migration's test, every test that derives its expectations from providers.json/providers.sample.json (tuiHandshake, cliChildEnv, harnesses, bootstrapSequence, providerModels, the two updated above), and the slotstream server/model-manager suites — all green. Two unrelated pre-existing failures on this Windows machine (aiToolkit/runner.test.js EBUSY temp-file cleanup, AIProviders.test.jsx a waitFor timing flake) were confirmed to reproduce identically on a clean main with none of this branch's changes applied, so they are not from this change. Co-Authored-By: Claude Sonnet 5 --- client/src/utils/providers.test.js | 2 +- data.reference/providers.json | 12 ++++ docs/features/product-surfaces.md | 2 +- docs/features/slotstream.md | 33 +++++++-- scripts/migrations/340-slotstream-provider.js | 39 +++++++++++ .../340-slotstream-provider.test.js | 70 +++++++++++++++++++ .../aiToolkit/defaults/providers.sample.json | 13 ++++ .../aiToolkit/internal/modelFetchers.test.js | 1 + 8 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 scripts/migrations/340-slotstream-provider.js create mode 100644 scripts/migrations/340-slotstream-provider.test.js diff --git a/client/src/utils/providers.test.js b/client/src/utils/providers.test.js index 91f3f47952..190d8bfcda 100644 --- a/client/src/utils/providers.test.js +++ b/client/src/utils/providers.test.js @@ -1138,7 +1138,7 @@ describe('supportsModelRefresh', () => { // provider — nothing here can enumerate that, and Models → Harnesses // ("Refresh models") is where their catalog comes from instead. 'opencode-zen', - 'openrouter', 'orcarouter', + 'openrouter', 'orcarouter', 'slotstream', ]); }); }); diff --git a/data.reference/providers.json b/data.reference/providers.json index 6cd444d6c8..22f65247f4 100644 --- a/data.reference/providers.json +++ b/data.reference/providers.json @@ -596,6 +596,18 @@ "enabled": false, "envVars": {} }, + "slotstream": { + "id": "slotstream", + "name": "Slotstream (SSD-streaming MoE)", + "type": "api", + "endpoint": "http://127.0.0.1:5564/v1", + "apiKey": "", + "models": ["qwen3-235b-a22b-4bit", "gpt-oss-120b-mxfp4", "qwen3-30b-a3b-4bit"], + "defaultModel": "qwen3-235b-a22b-4bit", + "timeout": 300000, + "enabled": false, + "envVars": {} + }, "nvidia-kimi": { "id": "nvidia-kimi", "name": "NVIDIA Kimi K2.5", diff --git a/docs/features/product-surfaces.md b/docs/features/product-surfaces.md index 23004c8bda..a94d00f3d0 100644 --- a/docs/features/product-surfaces.md +++ b/docs/features/product-surfaces.md @@ -194,7 +194,7 @@ Local AI model acceleration, multi-machine peer federation, storage classificati | Surface / Area | Route(s) | Key Capabilities & Workflows | Related Guides | |---|---|---|---| -| **Local LLM Runtimes** | `/models/llms` | Management of local model servers: Ollama, LM Studio, vLLM, SGLang, and llama.cpp / llama-server. | [Claude on Ollama](./claude-ollama.md) | +| **Local LLM Runtimes** | `/models/llms` | Management of local model servers: Ollama, LM Studio, vLLM, SGLang, llama.cpp / llama-server, MTPLX, and Slotstream (SSD-streaming MoE for checkpoints larger than RAM). | [Claude on Ollama](./claude-ollama.md), [MTPLX](./mtplx.md), [Slotstream](./slotstream.md) | | **Speculative Decoding** | `/models/llms` | Accelerated token generation using DSpark, DFlash 2, and MTPLX speculative drafting pairs. | [DFlash2 & DSpark](./dflash2.md), [MTPLX](./mtplx.md), [RTX 3090 vLLM](./qwen38-rtx3090.md), [SGLang Qwen](./sglang-qwen38.md) | | **Embeddings Management** | `/models/embeddings` | Local text embedding models (Nomic, Ollama) and pgvector semantic index configuration. | [STORAGE.md](../STORAGE.md) | | **LoRAs & Model Training** | `/models/loras`, `/models/training` | LoRA adapter discovery, Civitai downloads, image captioning, and local FLUX LoRA training dataset management. | — | diff --git a/docs/features/slotstream.md b/docs/features/slotstream.md index a534902fd2..e13ea5326b 100644 --- a/docs/features/slotstream.md +++ b/docs/features/slotstream.md @@ -72,18 +72,39 @@ same port would silently steal its traffic instead of failing loudly. progress over the `slotstream:download` socket event; the download resumes from a partial file if interrupted, and stalls (no bytes for 20 minutes by default) are abandoned rather than hung forever, resumable on the next - press. PortOS also accepts a plain Hugging Face `owner/name` outside the - curated list, as long as the repo is a Slotstream-loadable MoE checkpoint. - Nothing downloads without this explicit press. + press. Every downloaded file is checked against the hash Hugging Face + published for it (`expectedSha256` in `streamResumableDownload`) before it + counts as finished; a mismatch fails the install and removes the partial + file rather than leaving a silently corrupt checkpoint on disk. PortOS also + accepts a plain Hugging Face `owner/name` outside the curated list, as long + as the repo is a Slotstream-loadable MoE checkpoint. Nothing downloads + without this explicit press. 3. **Start Slotstream** on the card, or **Save configuration** to store the choice for a later on-demand start without starting it now. Either way, Slotstream never fetches weights at start time — a start reads `~/.slotstream/models` (or `SLOTSTREAM_MODEL_DIR`, see below) and refuses with a clear message if nothing servable is cached, rather than trying to fetch the requested checkpoint. -4. Point an `api`/`tui` provider at the Slotstream endpoint (or use the - `slotstream` preset if one exists in AI Providers) and use it for - supported tasks. +4. Enable the **Slotstream (SSD-streaming MoE)** preset on **AI Providers** — + see below — or point any `api`/`tui` provider at the endpoint by hand, and + use it for supported tasks. + +## AI Providers preset + +`data.reference/providers.json` ships a `slotstream` preset (`api` type, +`http://127.0.0.1:5564/v1`, **disabled by default** — the same +consent-before-cost posture as the download itself) and migration 340 seeds it +into existing installs. It offers the curated catalog's three checkpoint ids +as candidate models, defaulting to the 235B-class headline case; whichever +checkpoint is actually cached and started is what answers, regardless of which +catalog id the request names. + +Unlike MTPLX, Slotstream gets no `opencode-slotstream` CLI/TUI wrapper: it is +text-only, so it is not a valid CoS coding-agent runner. `isSlotstreamProvider` +(`server/services/slotstreamServerManager.js`) recognizes any `api`/`tui` +provider pointed at `PORTS.SLOTSTREAM` — including a hand-added one, not only +this preset — so `ensureSlotstreamProviderReady` lazy-starts the runtime for +it the same way. ## Memory plan diff --git a/scripts/migrations/340-slotstream-provider.js b/scripts/migrations/340-slotstream-provider.js new file mode 100644 index 0000000000..48094f67f8 --- /dev/null +++ b/scripts/migrations/340-slotstream-provider.js @@ -0,0 +1,39 @@ +/** + * Ship a disabled Slotstream provider preset to existing installs. + * + * Slotstream is a separately managed local runtime (SSD-streaming MoE) — + * see docs/features/slotstream.md. It answers ordinary text tasks through its + * loopback OpenAI-compatible endpoint the same way MTPLX's API preset does + * (migration 272): text-only, so no CLI/TUI variant is registered — Slotstream + * is not a valid CoS coding-agent runner. + * + * This migration deliberately does not install Slotstream, download a + * checkpoint, start a daemon, or contact its endpoint. The preset is disabled + * by default. An install that already owns this id is left untouched, + * preserving refreshed models and local endpoint edits. + * + * Kept in lockstep with data.reference/providers.json and + * server/lib/aiToolkit/defaults/providers.sample.json. This frozen literal is + * the historical upgrade payload; later default changes require a new + * migration rather than rewriting this record. + */ + +import { makeProviderSeedMigration } from './_lib.js'; + +const SLOTSTREAM_API = { + id: 'slotstream', + name: 'Slotstream (SSD-streaming MoE)', + type: 'api', + endpoint: 'http://127.0.0.1:5564/v1', + apiKey: '', + models: ['qwen3-235b-a22b-4bit', 'gpt-oss-120b-mxfp4', 'qwen3-30b-a3b-4bit'], + defaultModel: 'qwen3-235b-a22b-4bit', + timeout: 300000, + enabled: false, + envVars: {}, +}; + +export default makeProviderSeedMigration({ + label: 'Slotstream', + defs: [SLOTSTREAM_API], +}); diff --git a/scripts/migrations/340-slotstream-provider.test.js b/scripts/migrations/340-slotstream-provider.test.js new file mode 100644 index 0000000000..310cb15352 --- /dev/null +++ b/scripts/migrations/340-slotstream-provider.test.js @@ -0,0 +1,70 @@ +/** + * Test for migration 340 — add the Slotstream provider preset to existing + * installs. The shared idempotent write shell is asserted in _lib.test.js; + * this test pins migration 340's frozen payload and its disabled-by-default, + * text-only contract (no CLI/TUI variant — see migration 272 for why MTPLX + * gets one and Slotstream does not). + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdtempSync, rmSync, writeFileSync, readFileSync, mkdirSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; + +import migration from './340-slotstream-provider.js'; + +const writeJson = (path, value) => writeFileSync(path, JSON.stringify(value, null, 2) + '\n'); +const readJson = (path) => JSON.parse(readFileSync(path, 'utf-8')); + +describe('migration 340 — Slotstream provider', () => { + let rootDir; + let providersPath; + + beforeEach(() => { + rootDir = mkdtempSync(join(tmpdir(), 'migration-340-')); + mkdirSync(join(rootDir, 'data'), { recursive: true }); + providersPath = join(rootDir, 'data/providers.json'); + }); + + afterEach(() => { + rmSync(rootDir, { recursive: true, force: true }); + }); + + it('adds a disabled API preset without touching existing state', async () => { + writeJson(providersPath, { + activeProvider: 'claude-code', + providers: { 'claude-code': { id: 'claude-code', type: 'cli', command: 'claude' } }, + }); + + await migration.up({ rootDir }); + + const out = readJson(providersPath); + const api = out.providers.slotstream; + + expect(api).toMatchObject({ + id: 'slotstream', + type: 'api', + endpoint: 'http://127.0.0.1:5564/v1', + models: ['qwen3-235b-a22b-4bit', 'gpt-oss-120b-mxfp4', 'qwen3-30b-a3b-4bit'], + defaultModel: 'qwen3-235b-a22b-4bit', + enabled: false, + }); + + // Text-only: no opencode-slotstream CLI/TUI wrapper, unlike MTPLX. + expect(out.providers['opencode-slotstream']).toBeUndefined(); + expect(out.providers['opencode-slotstream-tui']).toBeUndefined(); + + expect(out.providers['claude-code']).toBeDefined(); + expect(out.activeProvider).toBe('claude-code'); + }); + + it('preserves an existing Slotstream provider instead of replacing its local edits', async () => { + const existing = { + id: 'slotstream', name: 'My Slotstream', type: 'api', endpoint: 'http://127.0.0.1:5564/v1', enabled: true, + }; + writeJson(providersPath, { providers: { slotstream: existing } }); + + await migration.up({ rootDir }); + + expect(readJson(providersPath).providers.slotstream).toEqual(existing); + }); +}); diff --git a/server/lib/aiToolkit/defaults/providers.sample.json b/server/lib/aiToolkit/defaults/providers.sample.json index 5e95d5ab39..be0003db4c 100644 --- a/server/lib/aiToolkit/defaults/providers.sample.json +++ b/server/lib/aiToolkit/defaults/providers.sample.json @@ -638,6 +638,19 @@ "envVars": {}, "secretEnvVars": [] }, + "slotstream": { + "id": "slotstream", + "name": "Slotstream (SSD-streaming MoE)", + "type": "api", + "endpoint": "http://127.0.0.1:5564/v1", + "apiKey": "", + "models": ["qwen3-235b-a22b-4bit", "gpt-oss-120b-mxfp4", "qwen3-30b-a3b-4bit"], + "defaultModel": "qwen3-235b-a22b-4bit", + "timeout": 300000, + "enabled": false, + "envVars": {}, + "secretEnvVars": [] + }, "grok": { "id": "grok", "name": "xAI Grok", diff --git a/server/lib/aiToolkit/internal/modelFetchers.test.js b/server/lib/aiToolkit/internal/modelFetchers.test.js index 4775c658e9..698172e3a9 100644 --- a/server/lib/aiToolkit/internal/modelFetchers.test.js +++ b/server/lib/aiToolkit/internal/modelFetchers.test.js @@ -18,6 +18,7 @@ const SHIPPED_REFRESHABLE = [ 'antigravity-cli', 'antigravity-tui', 'cerebras', 'claude-code', 'claude-code-bedrock', 'claude-ollama', 'claude-ollama-tui', 'cursor-cli', 'cursor-tui', 'grok', 'lmstudio', 'mtplx', 'nvidia-kimi', 'ollama', + 'slotstream', 'opencode-llama-tui', 'opencode-mtplx', 'opencode-mtplx-tui', 'opencode-ollama', 'opencode-ollama-tui', 'opencode-orcarouter', 'opencode-orcarouter-tui', 'orcarouter',