Merge pull request #47 from Routstr/syncing-sdk

Sync daemon with SDK discovery adapter API
This commit is contained in:
redshift
2026-07-05 16:00:51 +00:00
committed by GitHub
4 changed files with 52 additions and 27 deletions
-3
View File
@@ -37,7 +37,6 @@ type DaemonDeps = {
walletClient: CocodClient;
walletAdapter: any;
storageAdapter: any;
providerRegistry: any;
discoveryAdapter: any;
modelManager: any;
ensureProvidersBootstrapped: () => Promise<void>;
@@ -292,7 +291,6 @@ export function createDaemonRequestHandler(deps: {
walletClient: CocodClient;
walletAdapter: any;
storageAdapter: any;
providerRegistry: any;
discoveryAdapter: any;
modelManager: any;
ensureProvidersBootstrapped: () => Promise<void>;
@@ -1268,7 +1266,6 @@ export function createDaemonRequestHandler(deps: {
headers: incomingHeaders,
walletAdapter: deps.walletAdapter,
storageAdapter: deps.storageAdapter,
providerRegistry: deps.providerRegistry,
discoveryAdapter: deps.discoveryAdapter,
modelManager: deps.modelManager,
debugLevel: "DEBUG",
+26 -13
View File
@@ -6,15 +6,27 @@ import { getUsageSummary, __resetUsageSummaryCacheForTest } from "./usage-summar
// ─── Test fixtures ────────────────────────────────────────────────────────────
//
// All timestamps fall within 30 days of 2026-06-02 (today as of this writing).
// All timestamps are computed relative to "now" so the fixtures never age
// out of getUsageSummary's 30-day rolling window. We anchor to a recent UTC
// midnight ~10 days ago and keep the same hour-of-day structure the
// tz-bucketing assertions depend on.
//
// tz = 300 (UTC-5 / EST). After shifting by -300min, UTC dates become:
// d1: 2026-05-20T10:00Z → shifted 2026-05-20T05:00Z → local day "2026-05-20", hour 05
// d2: 2026-05-21T03:00Z → shifted 2026-05-20T22:00Z → local day "2026-05-20", hour 22
// d3: 2026-05-21T06:00Z → shifted 2026-05-21T01:00Z → local day "2026-05-21", hour 01
// d1: <anchor> 10:00Z → local <anchor> 05:00 → local day "<anchor>"
// d2: <anchor+1d> 03:00Z → local <anchor> 22:00 → local day "<anchor>"
// d3: <anchor+1d> 06:00Z → local <anchor+1d> 01:00 → local day "<anchor+1d>"
const D1 = new Date("2026-05-20T10:00:00Z").getTime(); // local 2026-05-20, hour 05
const D2 = new Date("2026-05-21T03:00:00Z").getTime(); // local 2026-05-20, hour 22
const D3 = new Date("2026-05-21T06:00:00Z").getTime(); // local 2026-05-21, hour 01
const _BASE_UTC_MIDNIGHT = Math.floor(Date.now() / 86_400_000) * 86_400_000;
const _ANCHOR_MS = _BASE_UTC_MIDNIGHT - 10 * 86_400_000; // 10 days ago UTC
const D1 = _ANCHOR_MS + 10 * 3_600_000; // 10:00Z → local day <anchor>
const D2 = _ANCHOR_MS + 1 * 86_400_000 + 3 * 3_600_000; // 03:00Z next day → local day <anchor>
const D3 = _ANCHOR_MS + 1 * 86_400_000 + 6 * 3_600_000; // 06:00Z next day → local day <anchor+1d>
/** Compute the local-day date string the same way the SDK's day grouping does. */
function localDayDateStr(ts: number, tzOffsetMinutes: number): string {
const d = new Date(ts - tzOffsetMinutes * 60_000);
return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}`;
}
const BASE_ENTRY: Omit<UsageTrackingEntry, "id" | "timestamp" | "modelId" | "client" | "totalTokens"> = {
baseUrl: "https://api.openai.com/",
@@ -152,23 +164,24 @@ describe("getUsageSummary", () => {
const summary = await getUsageSummary(driver, CLIENTS, TZ);
const { days } = summary;
// D1 (10:00Z) → local 2026-05-20, D2 (03:00Z) → local 2026-05-20, D3 (06:00Z) → local 2026-05-21
// So: day 2026-05-20 has e1+e2, day 2026-05-21 has e3+e4
// D1 (10:00Z) and D2 (next day 03:00Z) both bucket to local day <anchor>;
// D3 (next day 06:00Z) buckets to local day <anchor+1d>.
// So: day <anchor> has e1+e2, day <anchor+1d> has e3+e4
expect(days.length).toBeGreaterThanOrEqual(2);
// Most-recent-first: 2026-05-21 first
expect(days[0]!.date).toBe("2026-05-21");
// Most-recent-first: <anchor+1d> first
expect(days[0]!.date).toBe(localDayDateStr(D3, TZ));
expect(days[0]!.requests).toBe(2); // e3, e4
expect(days[0]!.satsCost).toBe(8 + 50);
expect(days[1]!.date).toBe("2026-05-20");
expect(days[1]!.date).toBe(localDayDateStr(D1, TZ));
expect(days[1]!.requests).toBe(2); // e1, e2
expect(days[1]!.satsCost).toBe(10 + 20);
});
it("returns empty hoursToday (test entries are not today)", async () => {
const summary = await getUsageSummary(driver, CLIENTS, TZ);
// Test entries are in May 2026, not today (June 2026)
// Test entries are ~10 days ago, not today
expect(summary.hoursToday).toHaveLength(0);
});
+13 -5
View File
@@ -39,7 +39,6 @@ import {
createBunSqliteDriver,
createBunSqliteUsageTrackingDriver,
createShardedDiscoveryAdapter,
createProviderRegistryFromDiscoveryAdapter,
} from "@routstr/sdk/storage/bun";
import { createWalletAdapter } from "./wallet";
import type { AutoRefillConfig } from "./wallet/auto-refill";
@@ -82,7 +81,6 @@ async function main(): Promise<void> {
});
const discoveryAdapter = await createShardedDiscoveryAdapter({ driver: sqliteDriver });
const providerRegistry = createProviderRegistryFromDiscoveryAdapter(discoveryAdapter, daemonSdkLogger);
const storageAdapter = createStorageAdapterFromStore(store);
const modelManager = new ModelManager(discoveryAdapter, {
logger: daemonSdkLogger,
@@ -91,7 +89,7 @@ async function main(): Promise<void> {
nostrRelays: config.relays,
});
// Create shared ProviderManager for consistent failure tracking across all requests
const providerManager = new ProviderManager(providerRegistry, store, daemonSdkLogger);
const providerManager = new ProviderManager(discoveryAdapter, store, daemonSdkLogger);
const { ensureProvidersBootstrapped, getRoutstr21Models, getModelProviders, refreshProvidersAndModels } =
createModelService(modelManager, store);
@@ -123,7 +121,7 @@ async function main(): Promise<void> {
const refundClient = new RoutstrClient(
walletAdapter,
storageAdapter,
providerRegistry,
discoveryAdapter,
"min",
"apikeys",
{ logger: daemonSdkLogger },
@@ -139,7 +137,6 @@ async function main(): Promise<void> {
walletClient,
walletAdapter,
storageAdapter,
providerRegistry,
discoveryAdapter,
modelManager,
ensureProvidersBootstrapped,
@@ -176,6 +173,13 @@ async function main(): Promise<void> {
);
refreshInterval = setInterval(async () => {
logger.log("Running scheduled Nostr event refresh...");
try {
await modelManager.refreshNostrEvents();
} catch (error) {
logger.error("Scheduled Nostr event refresh failed:", error);
}
logger.log("Running scheduled model refresh...");
try {
await refreshModelsAndIntegrations(getRoutstr21Models, updatedConfig, "Scheduled");
@@ -268,6 +272,10 @@ async function main(): Promise<void> {
// Start the recurring model refresh job after initial bootstrap
void ensureProvidersBootstrapped()
.then(async () => {
// Catch up on any Nostr events published since last run
logger.log("Running initial Nostr event refresh...");
await modelManager.refreshNostrEvents();
startModelRefreshJob();
startRefundJob();
// Run an immediate refresh to populate models right away
+13 -6
View File
@@ -278,18 +278,25 @@ export function renderToday(stats: UsageStats, width: number): string {
const _now = new Date();
const todayDateStr = `${_now.getFullYear()}-${String(_now.getMonth() + 1).padStart(2, "0")}-${String(_now.getDate()).padStart(2, "0")}`;
let todayStats: { date: string; requests: number; satsCost: number; promptTokens: number; completionTokens: number; totalTokens: number };
let recentDays: Array<{ date: string; requests: number; satsCost: number; totalTokens: number; promptTokens: number; completionTokens: number }>;
let hourlyMap: Map<number, { requests: number; satsCost: number }>;
let todayStats: { date: string; requests: number; satsCost: number; promptTokens: number; completionTokens: number; totalTokens: number } = {
date: todayDateStr,
requests: 0,
satsCost: 0,
promptTokens: 0,
completionTokens: 0,
totalTokens: 0,
};
let recentDays: Array<{ date: string; requests: number; satsCost: number; totalTokens: number; promptTokens: number; completionTokens: number }> = [];
let hourlyMap: Map<number, { requests: number; satsCost: number }> = new Map();
if (stats.summary) {
// Use server-aggregated data
const { days, hoursToday } = stats.summary;
// days[0] is most-recent-first; check if it's today
const todayDayStat = days[0]?.date === todayDateStr ? days[0] : undefined;
todayStats = todayDayStat
? { date: todayDayStat.date, requests: todayDayStat.requests, satsCost: todayDayStat.satsCost, promptTokens: todayDayStat.promptTokens, completionTokens: todayDayStat.completionTokens, totalTokens: todayDayStat.totalTokens }
: { date: todayDateStr, requests: 0, satsCost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
if (todayDayStat) {
todayStats = { date: todayDayStat.date, requests: todayDayStat.requests, satsCost: todayDayStat.satsCost, promptTokens: todayDayStat.promptTokens, completionTokens: todayDayStat.completionTokens, totalTokens: todayDayStat.totalTokens };
}
// Exclude today by date (it has its own box) rather than by position —
// days[0] is only today when there was activity today.
recentDays = days.filter((d) => d.date !== todayDateStr).slice(0, 6);