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.
This commit is contained in:
SkateSpots 2026-09-27 15:11:25 +07:00
parent a5a134c6c0
commit 00aab5369b
2 changed files with 16 additions and 4 deletions

View file

@ -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=<pubkey>`), 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 <node>/v2/lightning/invoice {amount_sats,purpose:"create"}` (or import a
Cashu token via `GET <node>/v1/balance/create?initial_balance_token=cashuA…`),
then set `SASQUATCH_AI_BASE=<node>/v1`, `SASQUATCH_AI_KEY=sk-…`,
`SASQUATCH_MODELS=<slugs>` 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,

View file

@ -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);