From 00aab5369b61c1a317de5be4302cd53d0365ef49 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Sun, 27 Sep 2026 15:11:25 +0700 Subject: [PATCH] feat(ai-proxy): server-authoritative models (provider switch = env only) If the client asks for a model not in the allowlist, the proxy now uses its configured default instead of 400-ing. So switching the upstream to a Routstr node is purely server-env (SASQUATCH_AI_BASE/KEY/MODELS) with no client redeploy. AGENTS.md documents Routstr discovery (kind 38421, endpoints in 'u' tags), the live nodes found, and the fund->key recipe. --- AGENTS.md | 12 ++++++++++-- netlify/functions/ai.mjs | 8 ++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c0c3a6d..383cd61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -126,8 +126,16 @@ session with **no other memory of this project**. So: override the Netlify gateway (any OpenAI-compatible endpoint). For a **sats-only** provider use Routstr (`SASQUATCH_AI_BASE=https://api.routstr.com/v1`, key = a cashu…/sk- session token; `SASQUATCH_MODELS` must match `PROXY_MODELS`). - ⚠️ As of 2026-09-27 **`api.routstr.com` returned 404 for every endpoint and its - model list was empty** — do not switch to it until it serves models. + ⚠️ The **aggregator `api.routstr.com` is DOWN** (404 everywhere) but the + network is alive: nodes announce on Nostr as **kind 38421** with the endpoint + in a `u` tag (`d=`), and ~9 were serving models on 2026-09-27 + (e.g. `https://ai.orangesync.tech`, `routstr.cypherpunk.today`, + `routstr.githappens.space`). To use a node: fund an `sk-` key via + `POST /v2/lightning/invoice {amount_sats,purpose:"create"}` (or import a + Cashu token via `GET /v1/balance/create?initial_balance_token=cashuA…`), + then set `SASQUATCH_AI_BASE=/v1`, `SASQUATCH_AI_KEY=sk-…`, + `SASQUATCH_MODELS=` in Netlify env — **no client redeploy needed** (the + server falls back to its configured model if the client's slug isn't allowed). **AI answers are Pro-only**: free users get the static FAQ/glossary + local relay lookups only (`if (isPro)` gates the AI block in `SasquatchGuide`). Pro access (`useSasquatchPro`) is granted by a provider-authored zap receipt, diff --git a/netlify/functions/ai.mjs b/netlify/functions/ai.mjs index e3debac..1c30a24 100644 --- a/netlify/functions/ai.mjs +++ b/netlify/functions/ai.mjs @@ -237,8 +237,12 @@ export default async function handler(request) { return json({ error: 'Invalid JSON' }, 400, headers); } - const model = String(body.model ?? ''); - if (!ALLOWED_MODELS.has(model)) return json({ error: 'Model not allowed' }, 400, headers); + // The server is authoritative on models: if the client asks for a slug we + // don't allow, use our configured default instead of erroring. That makes a + // provider switch a server-env change (SASQUATCH_MODELS) with no client + // redeploy needed — the client's list is just a preference. + const requested = String(body.model ?? ''); + const model = ALLOWED_MODELS.has(requested) ? requested : ALLOWED_MODELS.values().next().value; const messages = Array.isArray(body.messages) ? body.messages.slice(-20) : []; const maxTokens = Math.min(Number(body.max_tokens) || 400, 800);