v0.2.4 max-monthly union + /relay/policy

This commit is contained in:
local
2026-05-11 22:02:38 -05:00
parent 6797aae404
commit e612e8b8e8
6 changed files with 113 additions and 10 deletions
+66 -7
View File
@@ -1,14 +1,43 @@
import { sdk } from '../sdk'
import { configFile } from '../file-models/config.json'
const { InputSpec, Value } = sdk
const { InputSpec, Value, Variants } = 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.
// max: TierConfig } where TierConfig fields can be number or null.
// `null` means "no cap on this dimension." The relay reads this on
// every request via configFile's live-reload.
// Max-tier monthly credits is rendered as a union — operator can pick
// "unlimited" (the default) or specify a hard monthly cap. Surfaced
// as a Value.union so the StartOS UI gives a clean radio-style choice
// rather than relying on a sentinel like 0 = unlimited.
const maxMonthlyVariants = Variants.of({
unlimited: {
name: 'Unlimited',
spec: InputSpec.of({}),
},
limited: {
name: 'Limit to a specific amount',
spec: InputSpec.of({
amount: Value.number({
name: 'Monthly Limit',
description:
'Total credits each Max-tier install gets per calendar month.',
required: true,
default: 1000,
min: 0,
max: 1_000_000,
integer: true,
step: 1,
units: 'credits',
placeholder: null,
}),
}),
},
})
const inputSpec = InputSpec.of({
// Core tier knobs.
core_lifetime: Value.number({
@@ -65,10 +94,19 @@ const inputSpec = InputSpec.of({
placeholder: null,
}),
// Max tier knobs.
max_monthly: Value.union(
{
name: 'Max — Monthly Credits',
description:
'Max-tier users default to unlimited monthly credits. Switch to "Limit to a specific amount" to cap how many credits each Max install can spend per month.',
default: 'unlimited',
},
maxMonthlyVariants,
),
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.',
'Within the Max monthly allowance, how many credits may be served via Gemini (the rest spill to the operator-hardware fallback). Default 50.',
required: true,
default: 50,
min: 0,
@@ -103,16 +141,37 @@ export const adjustTierQuotas = sdk.Action.withInput(
} catch {
parsed = {}
}
// Translate the saved `max.monthly` value back into the union shape.
// null → unlimited; number → limited with that amount.
const savedMaxMonthly = parsed?.max?.monthly
const maxMonthlyUnion =
savedMaxMonthly == null
? { selection: 'unlimited' as const, value: {} }
: {
selection: 'limited' as const,
value: { amount: savedMaxMonthly },
}
return {
core_lifetime: parsed?.core?.lifetime ?? 10,
core_gemini_cap: parsed?.core?.geminiCapLifetime ?? 5,
pro_monthly: parsed?.pro?.monthly ?? 50,
pro_gemini_cap: parsed?.pro?.geminiCapMonthly ?? 25,
max_monthly: maxMonthlyUnion,
max_gemini_cap: parsed?.max?.geminiCapMonthly ?? 50,
}
},
async ({ effects, input }) => {
// Resolve the Max monthly union back to a primitive value for
// storage. The union arrives as { selection, value }; "unlimited"
// → null, "limited" → the inner amount.
const mmSel = (input.max_monthly as any)?.selection
const mmVal = (input.max_monthly as any)?.value
const maxMonthly =
mmSel === 'limited'
? Number(mmVal?.amount ?? 1000)
: null
const quotas = {
core: {
lifetime: input.core_lifetime ?? 10,
@@ -127,7 +186,7 @@ export const adjustTierQuotas = sdk.Action.withInput(
},
max: {
lifetime: null,
monthly: null,
monthly: maxMonthly,
geminiCapMonthly: input.max_gemini_cap ?? 50,
},
}
+3 -2
View File
@@ -4,8 +4,9 @@ import { v_0_2_0 } from './v0.2.0'
import { v_0_2_1 } from './v0.2.1'
import { v_0_2_2 } from './v0.2.2'
import { v_0_2_3 } from './v0.2.3'
import { v_0_2_4 } from './v0.2.4'
export const versionGraph = VersionGraph.of({
current: v_0_2_3,
other: [v_0_2_2, v_0_2_1, v_0_2_0, v_0_1_0],
current: v_0_2_4,
other: [v_0_2_3, v_0_2_2, v_0_2_1, v_0_2_0, v_0_1_0],
})
+13
View File
@@ -0,0 +1,13 @@
import { VersionInfo } from '@start9labs/start-sdk'
export const v_0_2_4 = VersionInfo.of({
version: '0.2.4:0',
releaseNotes: {
en_US:
'Max tier monthly credits is now operator-configurable via the Adjust Tier Quotas action — radio choice between "Unlimited" (default) and a specific monthly cap. New GET /relay/policy endpoint exposes the current tier-quota config so Recap installs can render dynamic copy (e.g. "10 relay credits" updates automatically when you change the Core lifetime cap).',
},
migrations: {
up: async ({ effects }) => {},
down: async ({ effects }) => {},
},
})