Package v0.2.12→v0.2.124: manifest, actions, version graph
This commit is contained in:
+24
-10
@@ -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)
|
||||
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user