initial relay scaffold

This commit is contained in:
local
2026-05-11 20:03:27 -05:00
commit b9d86fa303
58 changed files with 7609 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Operator-facing knob for tier-quota tuning without a code change or
// redeploy. The schema is { core: TierConfig, pro: TierConfig,
// max: TierConfig } where TierConfig is
// { lifetime: number|null, monthly: number|null, geminiCapMonthly: number|null }
// null means "no cap on this dimension." The relay reads this on every
// request via configFile's live-reload.
const inputSpec = InputSpec.of({
// Core tier knobs.
core_lifetime: Value.number({
name: 'Core — Lifetime Credits',
description:
'Total credits a Core (unlicensed) install can ever spend. Default 5.',
required: true,
default: 5,
min: 0,
max: 1_000_000,
integer: true,
step: 1,
units: 'credits',
placeholder: null,
}),
// Pro tier knobs.
pro_monthly: Value.number({
name: 'Pro — Monthly Credits',
description:
'Total credits a Pro user gets each calendar month. Resets on the 1st. Default 50.',
required: true,
default: 50,
min: 0,
max: 1_000_000,
integer: true,
step: 1,
units: 'credits',
placeholder: null,
}),
pro_gemini_cap: Value.number({
name: 'Pro — Gemini Cap (monthly)',
description:
'Within the Pro monthly allowance, how many credits may be served via Gemini (the rest spill to the operator-hardware fallback). Default 25.',
required: true,
default: 25,
min: 0,
max: 1_000_000,
integer: true,
step: 1,
units: 'credits',
placeholder: null,
}),
// Max tier knobs.
max_gemini_cap: Value.number({
name: 'Max — Gemini Cap (monthly)',
description:
'Max-tier users get unlimited total credits but a capped slice goes via Gemini. Default 50.',
required: true,
default: 50,
min: 0,
max: 1_000_000,
integer: true,
step: 1,
units: 'credits',
placeholder: null,
}),
})
export const adjustTierQuotas = sdk.Action.withInput(
'adjust-tier-quotas',
async ({ effects }) => ({
name: 'Adjust Tier Quotas',
description:
'Tune the per-tier monthly credit caps and Gemini exposure without redeploying. Changes apply to the next request — no restart needed.',
warning: null,
allowedStatuses: 'any',
group: null,
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
let parsed: any = {}
try {
parsed = JSON.parse(config?.relay_tier_quotas_json || '{}')
} catch {
parsed = {}
}
return {
core_lifetime: parsed?.core?.lifetime ?? 5,
pro_monthly: parsed?.pro?.monthly ?? 50,
pro_gemini_cap: parsed?.pro?.geminiCapMonthly ?? 25,
max_gemini_cap: parsed?.max?.geminiCapMonthly ?? 50,
}
},
async ({ effects, input }) => {
const quotas = {
core: {
lifetime: input.core_lifetime ?? 5,
monthly: null,
geminiCapMonthly: null,
},
pro: {
lifetime: null,
monthly: input.pro_monthly ?? 50,
geminiCapMonthly: input.pro_gemini_cap ?? 25,
},
max: {
lifetime: null,
monthly: null,
geminiCapMonthly: input.max_gemini_cap ?? 50,
},
}
await configFile.merge(effects, {
relay_tier_quotas_json: JSON.stringify(quotas),
})
return null
},
)
+15
View File
@@ -0,0 +1,15 @@
import { sdk } from '../sdk'
import { setGeminiKey } from './setGeminiKey'
import { setKeysatBaseUrl } from './setKeysatBaseUrl'
import { setParakeetUrl } from './setParakeetUrl'
import { setGemmaUrl } from './setGemmaUrl'
import { setAdminPassword } from './setAdminPassword'
import { adjustTierQuotas } from './adjustTierQuotas'
export const actions = sdk.Actions.of()
.addAction(setGeminiKey)
.addAction(setKeysatBaseUrl)
.addAction(setParakeetUrl)
.addAction(setGemmaUrl)
.addAction(setAdminPassword)
.addAction(adjustTierQuotas)
+107
View File
@@ -0,0 +1,107 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
import { randomBytes, scryptSync } from 'crypto'
const { InputSpec, Value } = sdk
const SCRYPT_KEYLEN = 64
// Mirror of Recap's setAdminPassword — same shape so server-side
// admin-auth code can be lifted with minimal change.
const inputSpec = InputSpec.of({
relay_admin_username: Value.text({
name: 'Admin Username',
description: 'Username for the relay admin dashboard. Defaults to "admin".',
required: true,
default: 'admin',
minLength: 1,
maxLength: 64,
}),
relay_admin_password: Value.text({
name: 'Admin Password',
description:
'Password for the relay admin dashboard. Must be at least 8 characters. Leave blank to disable /admin entirely (useful while testing /relay/* endpoints).',
required: false,
default: null,
masked: true,
minLength: 0,
maxLength: 256,
}),
relay_admin_password_confirm: Value.text({
name: 'Confirm Password',
description: 'Re-enter the password to confirm.',
required: false,
default: null,
masked: true,
minLength: 0,
maxLength: 256,
}),
})
export const setAdminPassword = sdk.Action.withInput(
'set-admin-password',
async ({ effects }) => ({
name: 'Set Admin Password',
description:
"Gate the relay's /admin dashboard. The public /relay/* endpoints are unaffected — they're per-call authenticated via X-Recap-Install-Id + Authorization headers.",
warning: null,
allowedStatuses: 'any',
group: null,
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
return {
relay_admin_username: config?.relay_admin_username || 'admin',
relay_admin_password: undefined,
relay_admin_password_confirm: undefined,
}
},
async ({ effects, input }) => {
const username = (input.relay_admin_username || '').trim()
const password = input.relay_admin_password || ''
const confirm = input.relay_admin_password_confirm || ''
if (!username) throw new Error('Username is required.')
if (password === '' && confirm === '') {
await configFile.merge(effects, {
relay_admin_username: username,
relay_admin_password_hash: '',
relay_admin_password_salt: '',
})
return null
}
if (password !== confirm) {
throw new Error('Password and confirmation do not match.')
}
if (password.length < 8) {
throw new Error('Password must be at least 8 characters.')
}
const salt = randomBytes(16).toString('hex')
const hash = scryptSync(password, salt, SCRYPT_KEYLEN).toString('hex')
const existing = await configFile.read().once()
const sessionSecret =
existing?.relay_admin_session_secret &&
existing.relay_admin_session_secret.length > 0
? existing.relay_admin_session_secret
: randomBytes(32).toString('hex')
await configFile.merge(effects, {
relay_admin_username: username,
relay_admin_password_hash: hash,
relay_admin_password_salt: salt,
relay_admin_session_secret: sessionSecret,
})
return null
},
)
+54
View File
@@ -0,0 +1,54 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// The operator's Gemini API key. This is the relay's primary backend
// — Recap requests for both transcribe and analyze go to Gemini first,
// and only spill to the optional Parakeet/Gemma backends once a user
// exceeds their tier's monthly Gemini cap.
//
// Free key from https://aistudio.google.com/apikey. Track usage in
// the Google AI Studio dashboard to know what tier pricing should be.
const inputSpec = InputSpec.of({
relay_gemini_api_key: Value.text({
name: 'Gemini API Key',
description:
'The relay\'s Google Gemini API key. Used for transcribe + analyze forwarding. Get one at https://aistudio.google.com/apikey',
required: true,
default: null,
masked: true,
minLength: 1,
maxLength: 256,
}),
})
export const setGeminiKey = sdk.Action.withInput(
'set-gemini-key',
async ({ effects }) => ({
name: 'Set Gemini API Key',
description:
"The operator's Gemini key. Required — the relay will refuse to serve traffic until this is set.",
warning: null,
allowedStatuses: 'any',
group: null,
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
return {
relay_gemini_api_key: config?.relay_gemini_api_key || undefined,
}
},
async ({ effects, input }) => {
await configFile.merge(effects, {
relay_gemini_api_key: input.relay_gemini_api_key,
})
return null
},
)
+55
View File
@@ -0,0 +1,55 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Optional Gemma/Ollama endpoint for the operator-hardware analysis
// fallback. Counterpart to setParakeetUrl — Parakeet handles transcribe
// overflow, this handles analyze overflow.
const inputSpec = InputSpec.of({
relay_gemma_base_url: Value.text({
name: 'Gemma Base URL',
description:
"URL of the operator's Gemma / Ollama / OpenAI-compatible analysis endpoint. Used as the overflow path once a user exceeds their monthly Gemini cap. Leave empty to hard-cap at the Gemini limit. Example: http://192.168.1.87:11434",
required: false,
default: '',
minLength: 0,
maxLength: 256,
patterns: [
{
regex: '^(https?://.+)?$',
description: 'Must be empty or start with http:// or https://',
},
],
}),
})
export const setGemmaUrl = sdk.Action.withInput(
'set-gemma-url',
async ({ effects }) => ({
name: 'Set Gemma URL',
description:
'Optional. Where the relay forwards analysis requests once a user exceeds their monthly Gemini cap. Leave empty to disable the fallback.',
warning: null,
allowedStatuses: 'any',
group: null,
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
return {
relay_gemma_base_url: config?.relay_gemma_base_url || '',
}
},
async ({ effects, input }) => {
await configFile.merge(effects, {
relay_gemma_base_url: (input.relay_gemma_base_url || '').trim(),
})
return null
},
)
+57
View File
@@ -0,0 +1,57 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Where the relay calls to validate licenses. Defaults to the public
// Keysat endpoint. Operators running Keysat on the same Start9 server
// can override to the internal hostname (e.g. http://keysat.startos:3000)
// for a lower-latency hot path — every relay request hits this for the
// cached online check.
const inputSpec = InputSpec.of({
relay_keysat_base_url: Value.text({
name: 'Keysat Base URL',
description:
"URL of the Keysat license server. Defaults to https://keysat.xyz. If you're running Keysat as a co-located StartOS package, override to the internal hostname (http://keysat.startos:<port>) to skip the public-internet roundtrip.",
required: true,
default: 'https://keysat.xyz',
minLength: 8,
maxLength: 256,
patterns: [
{
regex: '^https?://.+$',
description: 'Must start with http:// or https://',
},
],
}),
})
export const setKeysatBaseUrl = sdk.Action.withInput(
'set-keysat-base-url',
async ({ effects }) => ({
name: 'Set Keysat URL',
description:
"Where the relay validates Recap user licenses. Defaults to https://keysat.xyz — override to a co-located internal hostname if Keysat is on the same Start9 server.",
warning: null,
allowedStatuses: 'any',
group: null,
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
return {
relay_keysat_base_url: config?.relay_keysat_base_url || 'https://keysat.xyz',
}
},
async ({ effects, input }) => {
await configFile.merge(effects, {
relay_keysat_base_url: (input.relay_keysat_base_url || '').trim(),
})
return null
},
)
+59
View File
@@ -0,0 +1,59 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Optional Parakeet endpoint for the operator-hardware fallback path.
// When a Pro/Max user exceeds their Gemini monthly cap, the relay
// routes transcribe requests here instead. Empty disables the fallback
// — over-cap users get 402.
//
// In a typical setup this points at the operator's NVIDIA Spark or
// similar local GPU box running the NeMo / Parakeet HTTP wrapper.
const inputSpec = InputSpec.of({
relay_parakeet_base_url: Value.text({
name: 'Parakeet Base URL',
description:
'URL of the operator\'s Parakeet (or any Whisper-API-compatible) transcription endpoint. Used as the overflow path once a user exceeds their monthly Gemini cap. Leave empty to hard-cap at the Gemini limit. Example: http://192.168.1.87:8000',
required: false,
default: '',
minLength: 0,
maxLength: 256,
patterns: [
{
regex: '^(https?://.+)?$',
description: 'Must be empty or start with http:// or https://',
},
],
}),
})
export const setParakeetUrl = sdk.Action.withInput(
'set-parakeet-url',
async ({ effects }) => ({
name: 'Set Parakeet URL',
description:
"Optional. Where the relay forwards transcription requests once a user exceeds their monthly Gemini cap. Leave empty to disable the operator-hardware fallback.",
warning: null,
allowedStatuses: 'any',
group: null,
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
return {
relay_parakeet_base_url: config?.relay_parakeet_base_url || '',
}
},
async ({ effects, input }) => {
await configFile.merge(effects, {
relay_parakeet_base_url: (input.relay_parakeet_base_url || '').trim(),
})
return null
},
)