import { FileHelper } from '@start9labs/start-sdk' import { Volume } from '@start9labs/start-sdk/package/lib/util/Volume' import { z } from 'zod' const mainVolume = new Volume('main') // Operator-side configuration for the Recap Relay package. All fields // are optional and ship with sensible defaults — the relay will boot // even with an empty config, but will refuse to serve traffic until at // least relay_gemini_api_key and relay_admin_password_hash are set. export const configFile = FileHelper.json( { base: mainVolume, subpath: 'config/relay-config.json', }, z.object({ // ── Backend credentials ── // The relay's Gemini API key. Used for all transcribe + analyze // forwarding until a user exceeds their tier's Gemini cap (then // overflows to operator hardware below). Empty disables the // Gemini backend entirely — relay will then either route to // hardware (if configured) or 503 every request. relay_gemini_api_key: z.string().default(''), // ── Operator hardware (optional fallback) ── // When a Pro/Max user exceeds their monthly Gemini cap, the relay // routes overflow here. Leave empty to hard-cap at the Gemini limit // and return 402 once exceeded (no fallback). relay_parakeet_base_url: z.string().default(''), relay_gemma_base_url: z.string().default(''), // Model identifiers to send in the upstream request bodies. The // operator's Ollama or Parakeet wrapper may serve different models // depending on what's been pulled; making these config-driven // means the operator can swap models without rebuilding the relay. // Live-reloaded — change applies to the next request. relay_parakeet_model: z.string().default('parakeet-tdt-0.6b-v3'), relay_gemma_model: z.string().default('gemma3:27b'), // ── Gemini model selection ── // Operator can pick which Gemini SKU is used per pipeline step // without rebuilding the relay. Defaults match Google's typical // recommendations: Flash for transcription (cheap, fast, // multimodal-capable), Pro for analysis (higher quality on // structured-JSON outputs). Operators can swap to flash for // analysis when they want faster + cheaper at the cost of some // section-boundary precision. relay_gemini_transcription_model: z.string().default('gemini-3-flash-preview'), relay_gemini_analysis_model: z.string().default('gemini-3.1-pro-preview'), // ── Backend routing preference per pipeline ── // Controls whether the relay tries Gemini first (current default — // best quality, costs operator's Gemini API budget) or the // operator-hardware backend first (saves Gemini budget, may be // slower depending on the operator's hardware). One of: // - "gemini_first" try Gemini until per-tier cap, then hardware // - "hardware_first" try hardware first, fall back to Gemini // - "gemini_only" Gemini only, fail when cap is exceeded // - "hardware_only" Hardware only, fail when not configured relay_transcribe_backend_preference: z .enum(['gemini_first', 'hardware_first', 'gemini_only', 'hardware_only']) .default('gemini_first'), relay_analyze_backend_preference: z .enum(['gemini_first', 'hardware_first', 'gemini_only', 'hardware_only']) .default('gemini_first'), // ── License server ── // URL of the Keysat license server used for the cached online // license-validation check. Defaults to the public endpoint; // operators co-located with Keysat on the same Start9 server can // override to the internal `http://keysat.startos:` hostname // for a lower-latency hot path. relay_keysat_base_url: z.string().default('https://keysat.xyz'), // ── Admin dashboard auth ── // Username + scrypt-hashed password + session secret for the // /admin/* dashboard. Same shape Recap uses (see Recap's // server/admin-auth.js for the hash + verify code). Empty hash // disables /admin entirely — useful while testing the public // /relay/* endpoints. relay_admin_username: z.string().default(''), relay_admin_password_hash: z.string().default(''), relay_admin_password_salt: z.string().default(''), relay_admin_session_secret: z.string().default(''), // ── Tier quotas (operator-adjustable without redeploy) ── // JSON blob driving credits.js. Defaults match the v1 product // spec: Core lifetime-5, Pro 50/mo with 25 Gemini cap, Max // unlimited with 50 Gemini cap. Operators can tweak via the // "Adjust Tier Quotas" action without a code change or restart. relay_tier_quotas_json: z.string().default( JSON.stringify({ // Core: 10 lifetime credits total — first 5 served via Gemini // (operator's cloud spend), final 5 fall through to operator // hardware so the user can keep going on free tier without // costing the operator more cloud $. core: { lifetime: 10, geminiCapLifetime: 5, monthly: null, geminiCapMonthly: null, }, pro: { lifetime: null, monthly: 50, geminiCapMonthly: 25 }, max: { lifetime: null, monthly: null, geminiCapMonthly: 50 }, }), ), }), )