v0.2.3 Core tier 10/5/5 split + dynamic health version
This commit is contained in:
+17
-4
@@ -24,7 +24,12 @@ function defaultConfig() {
|
||||
relay_admin_password_salt: "",
|
||||
relay_admin_session_secret: "",
|
||||
relay_tier_quotas_json: JSON.stringify({
|
||||
core: { lifetime: 5, monthly: null, geminiCapMonthly: null },
|
||||
core: {
|
||||
lifetime: 10,
|
||||
geminiCapLifetime: 5,
|
||||
monthly: null,
|
||||
geminiCapMonthly: null,
|
||||
},
|
||||
pro: { lifetime: null, monthly: 50, geminiCapMonthly: 25 },
|
||||
max: { lifetime: null, monthly: null, geminiCapMonthly: 50 },
|
||||
}),
|
||||
@@ -68,14 +73,17 @@ export async function getConfigSnapshot() {
|
||||
}
|
||||
|
||||
// Parsed view of relay_tier_quotas_json, with safe fallbacks if the
|
||||
// blob is missing or malformed.
|
||||
// blob is missing or malformed. geminiCapLifetime is the new field
|
||||
// added in relay 0.2.3 — splits a Core install's lifetime budget into
|
||||
// Gemini-served vs hardware-served credits.
|
||||
export async function getTierQuotas() {
|
||||
const cfg = await getConfigSnapshot();
|
||||
try {
|
||||
const parsed = JSON.parse(cfg.relay_tier_quotas_json);
|
||||
return {
|
||||
core: {
|
||||
lifetime: parsed?.core?.lifetime ?? 5,
|
||||
lifetime: parsed?.core?.lifetime ?? 10,
|
||||
geminiCapLifetime: parsed?.core?.geminiCapLifetime ?? 5,
|
||||
monthly: parsed?.core?.monthly ?? null,
|
||||
geminiCapMonthly: parsed?.core?.geminiCapMonthly ?? null,
|
||||
},
|
||||
@@ -92,7 +100,12 @@ export async function getTierQuotas() {
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
core: { lifetime: 5, monthly: null, geminiCapMonthly: null },
|
||||
core: {
|
||||
lifetime: 10,
|
||||
geminiCapLifetime: 5,
|
||||
monthly: null,
|
||||
geminiCapMonthly: null,
|
||||
},
|
||||
pro: { lifetime: null, monthly: 50, geminiCapMonthly: 25 },
|
||||
max: { lifetime: null, monthly: null, geminiCapMonthly: 50 },
|
||||
};
|
||||
|
||||
+26
-3
@@ -8,9 +8,10 @@
|
||||
// {
|
||||
// install_id: "uuid",
|
||||
// tier_snapshot: "core" | "pro" | "max", // last-seen tier
|
||||
// lifetime_consumed: number, // for Core lifetime cap
|
||||
// lifetime_consumed: number, // total Core credits ever used
|
||||
// lifetime_gemini_consumed: number, // Core credits served by Gemini
|
||||
// month: "YYYY-MM", // calendar-month key
|
||||
// monthly_consumed: number, // total this month
|
||||
// monthly_consumed: number, // total this month (paid tiers)
|
||||
// monthly_gemini_consumed: number, // Gemini-only this month
|
||||
// last_active_at: ISO-8601 string,
|
||||
// }
|
||||
@@ -63,6 +64,7 @@ function blankRow(installId) {
|
||||
install_id: installId,
|
||||
tier_snapshot: "core",
|
||||
lifetime_consumed: 0,
|
||||
lifetime_gemini_consumed: 0,
|
||||
month: currentMonthKey(),
|
||||
monthly_consumed: 0,
|
||||
monthly_gemini_consumed: 0,
|
||||
@@ -103,16 +105,31 @@ export async function getOrCreateRow(installId) {
|
||||
// Returns:
|
||||
// { remaining: number | null, capped: "lifetime" | "monthly" | "none", gemini_remaining: number | null }
|
||||
// `null` for remaining means "unlimited" (Max tier total).
|
||||
// `null` for gemini_remaining means "no Gemini cap on this tier" — the
|
||||
// router can always pick Gemini.
|
||||
export function computeRemaining(row, quota) {
|
||||
const tier = row.tier_snapshot;
|
||||
const tierQuota = quota[tier] || quota.core;
|
||||
|
||||
if (tierQuota.lifetime != null) {
|
||||
const remaining = Math.max(0, tierQuota.lifetime - (row.lifetime_consumed || 0));
|
||||
// Core tier may carve out a portion of the lifetime budget for
|
||||
// Gemini specifically (geminiCapLifetime). When set, remaining
|
||||
// Gemini credits = cap - lifetime_gemini_consumed; the rest of
|
||||
// the lifetime budget falls through to operator hardware. When
|
||||
// null, lifetime tier ignores the Gemini/hardware split and uses
|
||||
// whichever backend is available.
|
||||
const geminiRemaining =
|
||||
tierQuota.geminiCapLifetime == null
|
||||
? null
|
||||
: Math.max(
|
||||
0,
|
||||
tierQuota.geminiCapLifetime - (row.lifetime_gemini_consumed || 0)
|
||||
);
|
||||
return {
|
||||
remaining,
|
||||
capped: "lifetime",
|
||||
gemini_remaining: null, // lifetime tier doesn't split Gemini/hardware
|
||||
gemini_remaining: geminiRemaining,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,11 +175,17 @@ export function planBackend(row, quota, { hasHardware }) {
|
||||
}
|
||||
|
||||
// Debit one credit on a successful call. Persists immediately.
|
||||
// Tracks Gemini-vs-hardware separately for Core (lifetime_gemini_consumed)
|
||||
// and paid tiers (monthly_gemini_consumed) so the planner can enforce
|
||||
// the per-tier Gemini cap.
|
||||
export async function commitCredit(installId, { backend, tier }) {
|
||||
const row = await getOrCreateRow(installId);
|
||||
row.tier_snapshot = tier;
|
||||
if (tier === "core") {
|
||||
row.lifetime_consumed = (row.lifetime_consumed || 0) + 1;
|
||||
if (backend === "gemini") {
|
||||
row.lifetime_gemini_consumed = (row.lifetime_gemini_consumed || 0) + 1;
|
||||
}
|
||||
} else {
|
||||
row.monthly_consumed = (row.monthly_consumed || 0) + 1;
|
||||
if (backend === "gemini") {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "recap-relay-server",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.3",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
+20
-1
@@ -3,8 +3,27 @@
|
||||
// /api/relay/status can verify the relay is reachable.
|
||||
|
||||
import express from "express";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { getConfigSnapshot } from "../config.js";
|
||||
|
||||
// Pull the version off server/package.json once at module load — the
|
||||
// build pipeline bumps that file in lockstep with each StartOS version
|
||||
// bump, so the health endpoint always reports a meaningful number.
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
let VERSION = "unknown";
|
||||
try {
|
||||
const pkg = JSON.parse(
|
||||
readFileSync(path.join(__dirname, "..", "package.json"), "utf8")
|
||||
);
|
||||
VERSION = pkg.version || "unknown";
|
||||
} catch {
|
||||
// Read failure is non-fatal — health still works, just reports
|
||||
// "unknown". Build pipeline shouldn't ever ship a missing
|
||||
// package.json so this branch is defensive only.
|
||||
}
|
||||
|
||||
export function healthRouter() {
|
||||
const router = express.Router();
|
||||
|
||||
@@ -13,7 +32,7 @@ export function healthRouter() {
|
||||
res.json({
|
||||
ok: true,
|
||||
service: "recap-relay",
|
||||
version: "0.1.0",
|
||||
version: VERSION,
|
||||
backends: {
|
||||
gemini: !!cfg.relay_gemini_api_key,
|
||||
parakeet: !!cfg.relay_parakeet_base_url,
|
||||
|
||||
Reference in New Issue
Block a user