Package v0.2.12→v0.2.124: manifest, actions, version graph

This commit is contained in:
Keysat
2026-06-13 13:36:30 -05:00
parent 318c6c4b81
commit 1243f4414c
126 changed files with 2052 additions and 441 deletions
+24 -10
View File
@@ -1,17 +1,31 @@
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'
import { setBackendRouting } from './setBackendRouting'
import { setTierPrices } from './setTierPrices'
// setBackendRouting removed in v0.2.50 — backend routing + Gemini
// model selection now live in the relay dashboard's Settings tab.
//
// Five more actions removed in v0.2.82 for the same reason:
// setGeminiKey, setKeysatBaseUrl, setSparkControlUrl,
// setParakeetUrl, setGemmaUrl
// All five fields are now inline-editable in the dashboard's Settings
// tab under "Endpoints & credentials". Same backing store
// (relay-config.json); the actions added a parallel surface for the
// same writes which created confusion about which path won. Single
// source of truth: the dashboard. The config keys themselves stay in
// relay-config.json — no migration needed.
//
// setAdminPassword stays here because it's the bootstrap mechanism:
// you can't sign into the dashboard until the password is set, so
// that one HAS to live in StartOS Actions.
import { setBtcpayConnection } from './setBtcpayConnection'
import { setZapriteConnection } from './setZapriteConnection'
import { setCreditPackages } from './setCreditPackages'
export const actions = sdk.Actions.of()
.addAction(setGeminiKey)
.addAction(setKeysatBaseUrl)
.addAction(setParakeetUrl)
.addAction(setGemmaUrl)
.addAction(setBackendRouting)
.addAction(setAdminPassword)
.addAction(adjustTierQuotas)
.addAction(setTierPrices)
.addAction(setBtcpayConnection)
.addAction(setZapriteConnection)
.addAction(setCreditPackages)
-154
View File
@@ -1,154 +0,0 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Lets the operator tune which backend gets tried first per pipeline
// step (transcribe vs analyze) AND which Gemini SKU is used when
// Gemini is the backend. All four knobs live-reload — change them
// via this action and the next relay request honors the new values
// without a daemon restart.
const inputSpec = InputSpec.of({
// ── Gemini model selection ──
// Both fields are radio-select with curated options. The relay's
// Gemini backend automatically falls back to lower-tier models in
// this same list when the chosen one returns a 503 / capacity /
// rate-limit error — see server/backends/gemini.js for the
// fallback-chain logic.
relay_gemini_transcription_model: Value.select({
name: 'Gemini Transcription Model',
description:
"Primary Gemini SKU used when a transcription request is routed to Gemini. On 503/capacity/rate-limit failure, the relay falls back to lower-tier models in order (e.g. 3-flash → 2.5-flash → 2.0-flash).",
default: 'gemini-3-flash-preview',
values: {
'gemini-3-flash-preview':
'Gemini 3 Flash — latest, recommended (~$0.30/M in, $2.50/M out)',
'gemini-2.5-flash':
'Gemini 2.5 Flash — prior gen (same pricing as 3-flash)',
'gemini-2.0-flash':
'Gemini 2.0 Flash — older + cheapest (~$0.10/M in, $0.40/M out)',
},
}),
relay_gemini_analysis_model: Value.select({
name: 'Gemini Analysis Model',
description:
"Primary Gemini SKU used when an analysis request is routed to Gemini. On 503/capacity/rate-limit failure, the relay falls back to lower-tier models in order (e.g. 3.1-pro → 3-pro → 3-flash → 2.5-flash).",
default: 'gemini-3.1-pro-preview',
values: {
'gemini-3.1-pro-preview':
'Gemini 3.1 Pro — best quality on structured-JSON output ($5/M in, $25/M out)',
'gemini-3-pro-preview':
'Gemini 3 Pro — prior Pro gen (same pricing as 3.1)',
'gemini-3-flash-preview':
'Gemini 3 Flash — faster + ~20× cheaper than Pro; some loss of section-boundary precision on long transcripts',
'gemini-2.5-flash':
'Gemini 2.5 Flash — prior Flash gen',
},
}),
// ── Backend routing preference per pipeline ──
relay_transcribe_backend_preference: Value.select({
name: 'Transcribe Backend Preference',
description:
'Routing strategy for transcription requests. The selected option controls the ORDER in which the relay tries each backend. The Gemini per-tier cap still applies regardless of this setting.',
default: 'gemini_first',
values: {
gemini_first:
'Gemini first → operator hardware (Parakeet) when cap exceeded',
hardware_first: 'Operator hardware first → Gemini as fallback',
gemini_only: 'Gemini only — fail when cap is exceeded',
hardware_only:
'Hardware only — fail when no Parakeet endpoint is configured',
},
}),
relay_analyze_backend_preference: Value.select({
name: 'Analyze Backend Preference',
description:
'Routing strategy for analysis requests. Same options as transcription but applies to the analyze step independently — you can route transcribe to hardware and analyze to Gemini, or vice versa.',
default: 'gemini_first',
values: {
gemini_first:
'Gemini first → operator hardware (Gemma) when cap exceeded',
hardware_first: 'Operator hardware first → Gemini as fallback',
gemini_only: 'Gemini only — fail when cap is exceeded',
hardware_only:
'Hardware only — fail when no Gemma endpoint is configured',
},
}),
})
export const setBackendRouting = sdk.Action.withInput(
'set-backend-routing',
async ({ effects }) => ({
name: 'Set Backend Routing & Models',
description:
"Tune which Gemini SKUs the relay uses and the per-pipeline backend pecking order. Live-reloaded — changes take effect on the next request, no restart.",
warning: null,
allowedStatuses: 'any',
group: 'AI Backends',
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
// Coerce any previously-saved model name to a value in the new
// select's options. Older 0.2.7-era saved configs could hold a
// free-text value that's no longer in the dropdown — clamp to a
// sensible default rather than presenting an invalid radio.
const TX_OPTIONS = [
'gemini-3-flash-preview',
'gemini-2.5-flash',
'gemini-2.0-flash',
] as const
const AN_OPTIONS = [
'gemini-3.1-pro-preview',
'gemini-3-pro-preview',
'gemini-3-flash-preview',
'gemini-2.5-flash',
] as const
const tx = config?.relay_gemini_transcription_model as
| (typeof TX_OPTIONS)[number]
| undefined
const an = config?.relay_gemini_analysis_model as
| (typeof AN_OPTIONS)[number]
| undefined
return {
relay_gemini_transcription_model:
tx && TX_OPTIONS.includes(tx) ? tx : 'gemini-3-flash-preview',
relay_gemini_analysis_model:
an && AN_OPTIONS.includes(an) ? an : 'gemini-3.1-pro-preview',
relay_transcribe_backend_preference:
(config?.relay_transcribe_backend_preference as
| 'gemini_first'
| 'hardware_first'
| 'gemini_only'
| 'hardware_only'
| undefined) || 'gemini_first',
relay_analyze_backend_preference:
(config?.relay_analyze_backend_preference as
| 'gemini_first'
| 'hardware_first'
| 'gemini_only'
| 'hardware_only'
| undefined) || 'gemini_first',
}
},
async ({ effects, input }) => {
await configFile.merge(effects, {
relay_gemini_transcription_model: (
input.relay_gemini_transcription_model || 'gemini-3-flash-preview'
).trim(),
relay_gemini_analysis_model: (
input.relay_gemini_analysis_model || 'gemini-3.1-pro-preview'
).trim(),
relay_transcribe_backend_preference: input.relay_transcribe_backend_preference,
relay_analyze_backend_preference: input.relay_analyze_backend_preference,
})
return null
},
)
-54
View File
@@ -1,54 +0,0 @@
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: 'AI Backends',
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
},
)
-67
View File
@@ -1,67 +0,0 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Operator's Gemma (or any OpenAI-compatible chat-completions) endpoint
// + which model to request. Both fields live-reload so the operator
// can pull a different Gemma SKU on Ollama and update the model name
// here without restarting the relay.
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://',
},
],
}),
relay_gemma_model: Value.text({
name: 'Gemma Model Name',
description:
'The model identifier sent in upstream chat-completions requests. Match whatever name your Ollama / vLLM / llama.cpp deployment exposes (run `ollama list` to see what you have pulled). Example: gemma3:27b, gemma2:9b, llama3.1:70b',
required: true,
default: 'gemma3:27b',
minLength: 1,
maxLength: 128,
}),
})
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 URL empty to disable the fallback.',
warning: null,
allowedStatuses: 'any',
group: 'AI Backends',
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
return {
relay_gemma_base_url: config?.relay_gemma_base_url || '',
relay_gemma_model: config?.relay_gemma_model || 'gemma3:27b',
}
},
async ({ effects, input }) => {
await configFile.merge(effects, {
relay_gemma_base_url: (input.relay_gemma_base_url || '').trim(),
relay_gemma_model: (input.relay_gemma_model || 'gemma3:27b').trim(),
})
return null
},
)
-57
View File
@@ -1,57 +0,0 @@
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: 'Setup',
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
},
)
-68
View File
@@ -1,68 +0,0 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
// Operator's Parakeet endpoint + which model to request. Both fields
// live-reload — change them via this action and the next relay request
// picks up the new values without a daemon restart.
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://',
},
],
}),
relay_parakeet_model: Value.text({
name: 'Parakeet Model Name',
description:
'The model identifier sent in upstream requests (the "model" field in the OpenAI Whisper API body). Match whatever name your Parakeet wrapper expects. Default: parakeet-tdt-0.6b-v3',
required: true,
default: 'parakeet-tdt-0.6b-v3',
minLength: 1,
maxLength: 128,
}),
})
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 URL empty to disable the operator-hardware fallback.",
warning: null,
allowedStatuses: 'any',
group: 'AI Backends',
visibility: 'enabled',
}),
inputSpec,
async ({ effects }) => {
const config = await configFile.read().once()
return {
relay_parakeet_base_url: config?.relay_parakeet_base_url || '',
relay_parakeet_model:
config?.relay_parakeet_model || 'parakeet-tdt-0.6b-v3',
}
},
async ({ effects, input }) => {
await configFile.merge(effects, {
relay_parakeet_base_url: (input.relay_parakeet_base_url || '').trim(),
relay_parakeet_model:
(input.relay_parakeet_model || 'parakeet-tdt-0.6b-v3').trim(),
})
return null
},
)