Files
OmniRoute/src/lib/radar/index.ts
Diego Rodrigues de Sa e Souza a1c864373a referrals from standalone /v1/referrals feed (no 30-day delay) (#9762)
* feat(radar): sync referral links from standalone /v1/referrals/latest feed

Referral links previously came from the catalog feed cache, which on the
community tier can be up to 30 days stale -- a newly-added referral would
not reach a free/community user for up to a month. Adds a new sync module
(syncRadarReferrals), Ed25519-verified feed schema, and a dedicated
radar_referrals_cache table (migration 142) so referrals sync on their own,
much shorter cadence instead of inheriting the catalog's delay.

getRadarReferrals()/getDefaultReferralFor() now read the new cache instead
of the catalog feed's embedded referrals field (kept on RadarFeedSchema for
backward-compat with already-cached catalog feeds, but no longer read).

* feat(radar): wire sync-on-read + scheduler side-sync for referrals

GET /api/radar/referrals now triggers syncRadarReferrals() inline whenever
the cache is missing or older than 1h (shouldSyncReferralsOnRead), so fixed
links show up promptly on the next dashboard load instead of waiting on a
background timer. The route itself still never talks to the upstream feed
server directly -- syncRadarReferrals() remains the only network touchpoint.

radarSchedulerTick() also evaluates referrals staleness on the same hourly
tick used for the catalog, independent of the catalog's own due-ness, as a
best-effort side effect that never changes RadarTickResult's shape and is
swallowed on error.

* docs(radar): document the standalone referrals feed sync

Explains the /v1/referrals/latest feed, its no-tier-field-in-body design
(x-omniroute-feed-tier header is the only tier source), the sync-on-read +
scheduler side-sync triggers, and the self-hosting note for forks that only
serve the catalog feed.

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-08 08:10:20 -03:00

208 lines
6.4 KiB
TypeScript

/**
* index.ts — Radar client public accessor.
*
* Thin entry point for the UI screens. Returns the merged catalog
* (baseline + feed overlay) when Radar is active, or the raw baseline
* when the flag is off / no cache / corrupt cache.
*
* This module is the ONLY thing the screens import from `@/lib/radar`.
* All heavy lifting (sync, verify, schema, merge) lives in sibling files.
*/
import { FREE_MODEL_BUDGETS } from "@omniroute/open-sse/config/freeModelCatalog";
import { RadarFeedSchema, type RadarFeed, type RadarReferral } from "./feedSchema";
import { RadarReferralsFeedSchema, type RadarReferralsFeed } from "./referralsFeedSchema";
import { applyFeed, type MergedEntry, type FeedModel } from "./applyFeed";
import { findDefaultReferral } from "./referrals";
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
import { getRadarCache, getRadarReferralsCache } from "@/lib/db/radar";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface RadarCatalogResult {
/** Merged catalog entries. */
entries: MergedEntry[];
/** Feed metadata — null when falling back to baseline. */
meta: {
version: string;
tier: string;
fetchedAt: string;
} | null;
}
/** Injectable deps for testing. */
export interface GetRadarCatalogDeps {
getFlag?: (key: string) => boolean;
getCache?: () => { version: string; tier: string; payload: string; fetchedAt: string } | null;
baseline?: MergedEntry[];
localOverrides?: Map<string, Partial<MergedEntry>>;
tombstones?: Set<string>;
}
// ---------------------------------------------------------------------------
// Baseline converter
// ---------------------------------------------------------------------------
/**
* Convert the static `FreeModelBudget[]` into `MergedEntry[]` so the
* merge function has a uniform input shape.
*/
export function baselineToMergedEntries(
budgets: typeof FREE_MODEL_BUDGETS,
): MergedEntry[] {
return budgets.map((b) => ({
provider: b.provider,
modelId: b.modelId,
displayName: b.displayName,
monthlyTokens: b.monthlyTokens,
creditTokens: b.creditTokens,
freeType: b.freeType,
poolKey: b.poolKey ?? null,
tos: b.tos,
trainsOnPrompts: b.trainsOnPrompts,
enabled: true,
origin: "baseline" as const,
}));
}
// ---------------------------------------------------------------------------
// getRadarCatalog
// ---------------------------------------------------------------------------
/**
* Return the merged Radar catalog.
*
* Falls back to the static baseline (no overlay) when:
* - The `RADAR_ENABLED` flag is off.
* - There is no cached feed yet.
* - The cached payload fails defensive re-validation.
*
* @param deps - Injectable dependencies for testing.
*/
export function getRadarCatalog(deps: GetRadarCatalogDeps = {}): RadarCatalogResult {
const {
getFlag = isFeatureFlagEnabled,
getCache: getCacheFn = getRadarCache,
baseline: baselineInput,
localOverrides = new Map(),
tombstones = new Set(),
} = deps;
// Resolve baseline
const baseline = baselineInput ?? baselineToMergedEntries(FREE_MODEL_BUDGETS);
// Flag gate
const flagOn = getFlag("RADAR_ENABLED");
if (!flagOn) {
return { entries: baseline, meta: null };
}
// Cache gate
const cache = getCacheFn();
if (!cache) {
return { entries: baseline, meta: null };
}
// Defensive parse
let feed: RadarFeed;
try {
const parsed = JSON.parse(cache.payload);
feed = RadarFeedSchema.parse(parsed);
} catch {
return { entries: baseline, meta: null };
}
// Apply overlay
const entries = applyFeed({
baseline,
feed: feed.models as FeedModel[],
localOverrides,
tombstones,
});
return {
entries,
meta: {
version: cache.version,
tier: cache.tier,
fetchedAt: cache.fetchedAt,
},
};
}
// ---------------------------------------------------------------------------
// getRadarReferrals / getDefaultReferralFor
// ---------------------------------------------------------------------------
export interface RadarReferralsResult {
fixed: RadarReferral[];
campaigns: RadarReferral[];
}
const EMPTY_REFERRALS: RadarReferralsResult = { fixed: [], campaigns: [] };
/**
* Injectable deps for testing. `getCache` now reads the STANDALONE referrals
* feed cache (`radar_referrals_cache`, populated by
* `syncRadarReferrals()`/`GET /v1/referrals/latest`) — no longer the
* catalog's `radar_feed_cache`. This is what removes the up-to-30-day
* community-tier delay referral links used to inherit from the catalog
* feed: referrals now sync on their own, much shorter cadence
* (`REFERRALS_STALE_MS`, see `referralsSync.ts`).
*/
export interface GetRadarReferralsDeps {
getFlag?: (key: string) => boolean;
getCache?: () => { generatedAt: string; tier: string; payload: string; fetchedAt: string } | null;
}
/**
* Return the referral links section of the cached Radar REFERRALS feed
* (`GET /v1/referrals/latest` — see `referralsSync.ts`).
*
* Never throws — returns `{fixed:[],campaigns:[]}` when: the flag is off,
* there is no cache yet, or the cached payload fails defensive
* re-validation (corrupt/garbage payload).
*/
export function getRadarReferrals(deps: GetRadarReferralsDeps = {}): RadarReferralsResult {
const { getFlag = isFeatureFlagEnabled, getCache: getCacheFn = getRadarReferralsCache } = deps;
if (!getFlag("RADAR_ENABLED")) {
return EMPTY_REFERRALS;
}
const cache = getCacheFn();
if (!cache) {
return EMPTY_REFERRALS;
}
let feed: RadarReferralsFeed;
try {
const parsed = JSON.parse(cache.payload);
feed = RadarReferralsFeedSchema.parse(parsed);
} catch {
return EMPTY_REFERRALS;
}
return feed.referrals ?? EMPTY_REFERRALS;
}
/**
* Return the fixed, isDefault:true referral link for `provider`, or `null`
* when there isn't one (flag off, no cache, or no matching default). Only
* looks at `fixed` referrals — see `findDefaultReferral` in `./referrals.ts`.
*/
export function getDefaultReferralFor(
provider: string,
deps: GetRadarReferralsDeps = {},
): RadarReferral | null {
const { fixed } = getRadarReferrals(deps);
return findDefaultReferral(fixed, provider);
}
// Re-export merge types for convenience
export { applyFeed, type MergedEntry, type FeedModel } from "./applyFeed";
export { findDefaultReferral } from "./referrals";
export type { RadarReferral } from "./feedSchema";