initial relay scaffold
This commit is contained in:
@@ -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
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user