import { sdk } from '../sdk' import { configFile } from '../file-models/config.json' 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 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({ name: 'Core — Lifetime Credits', description: 'Total credits a Core (unlicensed) install can ever spend across its lifetime. Default 10 (5 served via Gemini + 5 via operator hardware).', required: true, default: 10, min: 0, max: 1_000_000, integer: true, step: 1, units: 'credits', placeholder: null, }), core_gemini_cap: Value.number({ name: 'Core — Gemini Cap (lifetime)', description: 'Within the Core lifetime allowance, how many credits may be served via Gemini (the rest spill to the operator-hardware fallback). 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 billing period. Renewals are calendar-anniversary based: a user who first activates on the 17th renews on the 17th of every following month (clamps to the last day for shorter months — e.g. Jan 31 → Feb 28/29 → Mar 31). 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_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', variants: maxMonthlyVariants, }), max_gemini_cap: Value.number({ name: 'Max — Gemini Cap (monthly)', description: '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, 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: 'Tiers', 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 = {} } // 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, geminiCapLifetime: input.core_gemini_cap ?? 5, monthly: null, geminiCapMonthly: null, }, pro: { lifetime: null, monthly: input.pro_monthly ?? 50, geminiCapMonthly: input.pro_gemini_cap ?? 25, }, max: { lifetime: null, monthly: maxMonthly, geminiCapMonthly: input.max_gemini_cap ?? 50, }, } await configFile.merge(effects, { relay_tier_quotas_json: JSON.stringify(quotas), }) return null }, )