feat(aiui): Routstr as an explicit, session-gated AI provider path

The D-04 Routstr leg was fallback-only — never user-selectable, and its
Nostr discovery parses a docs-shaped event content ({endpoints, models,
pricing}) that live kind-38421 announcements don't actually carry
({name, about}), so it could never match a real provider. This adds the
explicit path AIUI's model picker needs: /aiui/api/routstr/models
passes through the live aggregator catalog (the instance routstr.com's
own frontend queries; the canonical api.routstr.com 404s), and
/aiui/api/routstr/chat/completions makes one paid, non-streaming,
OpenAI-shaped call — session-gated, egress-screened (S3), refused
without an armed operator budget (D-05), paid via auto_pay_token,
change and refused-request tokens redeemed back into the wallet so a
failed attempt nets zero (verified live: quoted=1 reclaimed=1 net=0).
nginx template gains the location in both server blocks (T-13-15).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-14 13:41:07 -04:00
co-authored by Claude Fable 5
parent ced95a60d1
commit e3bd340725
4 changed files with 582 additions and 6 deletions
+9
View File
@@ -6,6 +6,7 @@ mod node_message;
mod proxy;
mod remote_input;
mod remote_relay;
mod routstr_proxy;
mod websocket;
use crate::api::rpc::RpcHandler;
@@ -449,6 +450,14 @@ impl ApiHandler {
self.handle_model_proxy(req_with_bytes, p).await
}
// AIUI Routstr proxy — the explicit, user-selected Routstr
// provider (model catalog + Cashu-paid completions), same
// session-gate discipline as the model proxy above. D-05: paid
// requests are refused unless the operator has armed a budget.
(_, p) if p.starts_with("/aiui/api/routstr/") => {
self.handle_routstr_proxy(req_with_bytes, p).await
}
// Health — unauthenticated, returns JSON with service status
(Method::GET, "/health") => {
let recovery_complete = crate::crash_recovery::is_recovery_complete();