From 91b5b04d97ec6d4d9adff06e3d4489d4c32346ce Mon Sep 17 00:00:00 2001 From: Keysat Date: Fri, 19 Jun 2026 14:47:30 -0500 Subject: [PATCH] =?UTF-8?q?v1.2.0:7=20=E2=80=94=20add=20SparkControl=20AI?= =?UTF-8?q?=20provider=20+=20fix=20base-URL=20footgun?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SparkControl is a self-hosted local-inference gateway with an OpenAI-compatible API, reached over the internal same-box StartOS address (http://spark-control.startos:9999/v1, plain HTTP). It takes no API key, so generateOpenAIStyle gained a { requireApiKey } option and now omits the Authorization header when no key is set. The Settings form auto-detects the loaded vLLM model via SparkControl's /api/endpoints probe, mirroring the Ollama auto-detect; it's $0 in the cost UI. Custom-URL => admin-only + SSRF-guarded, same as Ollama. Also fixes a config footgun behind the empty-response report: a custom base URL could ride along to a fixed-URL provider (claude/openai/gemini) whose form field is hidden, get stored, and be silently ignored (the provider always hits its hardcoded endpoint). Both config write paths now null baseUrl for non-custom-URL providers, and the form clears it on provider change. No schema/data change (AIConfigProfile.provider is free-text). 259 tests pass; built + sideloaded to immense-voyage.local with a clean non-root launch. --- AGENTS.md | 10 +- docs/guides/ai-subsystem.md | 27 ++- .../app/api/ai/configs/[id]/route.ts | 9 +- proof-of-work/app/api/ai/configs/route.ts | 34 +++- .../app/api/ai/sparkcontrol/model/route.ts | 109 +++++++++++ proof-of-work/app/api/ai/test/route.ts | 2 +- .../components/settings/AIIntegration.tsx | 84 ++++++++- proof-of-work/lib/ai/pricing.ts | 7 +- proof-of-work/lib/ai/providers/index.ts | 9 +- proof-of-work/lib/ai/providers/openai.ts | 8 +- .../lib/ai/providers/sparkcontrol.ts | 50 ++++++ proof-of-work/lib/ai/types.ts | 3 +- proof-of-work/tests/ai-sparkcontrol.test.ts | 99 ++++++++++ proof-of-work/tests/routes-ai-configs.test.ts | 170 ++++++++++++++++++ start9/0.4/startos/versions/index.ts | 8 +- start9/0.4/startos/versions/v1.2.0.7.ts | 33 ++++ 16 files changed, 635 insertions(+), 27 deletions(-) create mode 100644 proof-of-work/app/api/ai/sparkcontrol/model/route.ts create mode 100644 proof-of-work/lib/ai/providers/sparkcontrol.ts create mode 100644 proof-of-work/tests/ai-sparkcontrol.test.ts create mode 100644 proof-of-work/tests/routes-ai-configs.test.ts create mode 100644 start9/0.4/startos/versions/v1.2.0.7.ts diff --git a/AGENTS.md b/AGENTS.md index dc2791f..58b7691 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ proof-of-work/ ← the Next.js app (THIS is where you run npm) app/main/ ← authed UI; navigation.tsx = sidebar components/ ← React components (workouts/, ai/, settings/) lib/ai/ ← AI subsystem (see below) - lib/ai/providers/ ← claude.ts openai.ts gemini.ts ollama.ts + index.ts (getProvider; openai.ts exports both openai + openai-compatible = 5 registered providers) + lib/ai/providers/ ← claude.ts openai.ts gemini.ts ollama.ts sparkcontrol.ts + index.ts (getProvider; openai.ts exports both openai + openai-compatible = 6 registered providers) prisma/schema.prisma ← schema (mirror; real DB migrates via entrypoint ALTERs) prisma/*.seed.json ← curated exercise library + AI templates (reconciled each boot) tests/ ← Vitest specs (ai-*.test.ts, routes-*.test.ts, ...) @@ -116,15 +116,15 @@ Canonical publish path for this project: `~/.proof-of-work/publish.sh` (builds, ## Current state -Latest version is **1.2.0:6** — **AI "generate today's workout"**: describe one session in plain words → a streamed, ready-to-log workout (suggested weights/reps/set-counts grounded in 90-day history) → inline-edit or **Refine** (round-trips changes back to the LLM) → **Use this workout** pre-fills the New Workout form (nothing persists until you save). Reuses the program-generation spine via a new `AIGeneration.kind` discriminant (`"program" | "workout"`); workout rows are ephemeral (the saved Workout is the durable record) so they're filtered out of the program-shaped AI History. New `AIGeneration.kind` column (default "program") via boot-time guarded `ALTER`. **Architecture + hand-off (sessionStorage `?from=ai` → `AiWorkoutPrefill`, `EditWorkoutData.id?`→CREATE, per-exercise weight-unit grounding) are documented in `docs/guides/ai-subsystem.md` → "Two generation kinds".** **Built + sideloaded** (`immense-voyage.local`, 2026-06-19, `master`) as `proof-of-work_x86_64.s9pk` (80M). tsc clean (app + packaging), lint clean (pre-existing only), **251 tests pass**, `next build` + s9pk build succeed. Registry empty, **publishing parked** (sideload-only via `make install`). +Latest version is **1.2.0:7** — **SparkControl AI provider + base-URL footgun fix**. Adds a 6th provider, **SparkControl (local)** — the operator's own self-hosted local-inference gateway. OpenAI-compatible wire format (reuses `generateOpenAIStyle`), **keyless** on the LAN (`requireApiKey:false` → no `Authorization` header), reached over the **internal same-box StartOS address** `http://spark-control.startos:9999/v1` (plain HTTP — no TLS, no cert-skip; the public LAN interface is HTTPS w/ a self-signed cert we deliberately avoid). The Settings form **auto-detects the loaded vLLM model** via SparkControl's `/api/endpoints` (`app/api/ai/sparkcontrol/model`, admin-only + SSRF-guarded), mirroring Ollama auto-detect; $0 in the cost UI. Also fixes a **config footgun** (origin of this session): a custom base URL could ride along to a fixed-URL provider (claude/openai/gemini) whose form field is hidden, get stored, and be **silently ignored** (the provider hits its hardcoded endpoint) — both config write paths (`configs` POST + `[id]` PATCH) now null `baseUrl` for non-custom-URL providers and the form clears it on provider change. **No schema/data change** (`AIConfigProfile.provider` is a free-text column). Details: `docs/guides/ai-subsystem.md` → Provider abstraction (incl. the new "adding a provider" fan-out checklist). **Built + sideloaded** (`immense-voyage.local`, 2026-06-19, `master`) as `proof-of-work_x86_64.s9pk` (80M). tsc clean (app + packaging), lint clean (pre-existing only), **259 tests pass**, `next build` + s9pk build succeed. Registry empty, **publishing parked** (sideload-only via `make install`). **Design contract established this session (2026-06-19, committed `7fda9ce`, no UI code changed):** the `design/` folder now holds the durable contract — `DESIGN.md` (9-section brief) + `tokens.tokens.json` (DTCG) + `brand/palette.css` + `inspiration/` provenance. From a Case-B *document-as-is* extract of the as-built dark UI: **monochrome gym-brutalist** (`#0A0A0A` canvas, zinc-only neutral, white primary button, Bebas-uppercase/tracked headings, flat border-based depth), plus two owner calls — **red elevated to the single brand accent `#DC2626`** and a **two-tier radius** (4px controls / 8px containers). AGENTS.md carries the read-before-UI Design line; `ROADMAP.md` → **Design** holds the `design-checker` cleanup backlog (gray→zinc, green→emerald, yellow→amber, `rounded-md`→`rounded`, overlay-only shadows, and a shared `