From 7e1bd78ffef04810272c94d99b3d9f0baff41eaa Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Fri, 13 Mar 2026 12:40:21 +0000 Subject: [PATCH] logging usage --- src/daemon.ts | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/src/daemon.ts b/src/daemon.ts index 4ec0689..babf1d0 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -242,6 +242,78 @@ type UsageData = { satsCost: number; }; +type UsageTrackingEntry = UsageData & { + id: string; + timestamp: number; + modelId: string; + baseUrl: string; + requestId: string; + client?: string; + sessionId?: string; + tags?: string[]; +}; + +function extractUsageFromResponseBody(body: unknown): UsageData | null { + if (!body || typeof body !== "object") return null; + const usage = (body as { usage?: Record }).usage; + if (!usage || typeof usage !== "object") return null; + + const promptTokens = Number(usage.prompt_tokens ?? 0); + const completionTokens = Number(usage.completion_tokens ?? 0); + const totalTokens = Number(usage.total_tokens ?? 0); + const costValue = usage.cost; + + let cost = 0; + let satsCost = 0; + + if (typeof costValue === "number") { + cost = costValue; + } else if (costValue && typeof costValue === "object") { + const costObj = costValue as Record; + const totalUsd = costObj.total_usd; + const totalMsats = costObj.total_msats; + + cost = typeof totalUsd === "number" ? totalUsd : 0; + satsCost = typeof totalMsats === "number" ? totalMsats / 1000 : 0; + } + + if ( + promptTokens === 0 && + completionTokens === 0 && + totalTokens === 0 && + cost === 0 && + satsCost === 0 + ) { + return null; + } + + return { + promptTokens, + completionTokens, + totalTokens, + cost, + satsCost, + }; +} + +function resolveUsageBaseUrl(response: Response, fallback?: string): string { + const responseWithBaseUrl = response as Response & { baseUrl?: unknown }; + if (typeof responseWithBaseUrl.baseUrl === "string") { + return responseWithBaseUrl.baseUrl; + } + + try { + if (response.url) { + const parsed = new URL(response.url); + return `${parsed.protocol}//${parsed.host}`; + } + } catch { + // Ignore URL parsing failures. + } + + return fallback || "unknown"; +} + function createSSEParserTransform( onUsage: (usage: UsageData) => void, ): Transform { @@ -342,6 +414,13 @@ async function main(): Promise { const sqliteDriver = createBunSqliteDriver(DB_PATH); const store = await createSdkStore({ driver: sqliteDriver }); + const appendUsageTracking = (entry: UsageTrackingEntry): void => { + const state = store.getState(); + const nextUsage = [...(state.usageTracking || []), entry]; + state.setUsageTracking(nextUsage); + logger.log("Usage tracking saved:", JSON.stringify(entry)); + }; + // Create adapters from our SQLite-backed store const discoveryAdapter = createDiscoveryAdapterFromStore(store); const providerRegistry = createProviderRegistryFromStore(store); @@ -792,6 +871,7 @@ async function main(): Promise { const requestId = response.headers.get("x-routstr-request-id") || undefined; logger.log("Request ID, ", requestId, " with path: ", url.pathname); + const usageBaseUrl = resolveUsageBaseUrl(response, forcedProvider); if (isStream) { res.statusCode = response.status; @@ -812,6 +892,14 @@ async function main(): Promise { res.on("finish", () => { if (capturedUsage) { + appendUsageTracking({ + id: `${requestId || `req-${Date.now()}`}-${modelId}`, + timestamp: Date.now(), + modelId, + baseUrl: usageBaseUrl, + requestId: requestId || "unknown", + ...capturedUsage, + }); logger.log( `Streaming request usage:`, JSON.stringify(capturedUsage), @@ -825,6 +913,17 @@ async function main(): Promise { } const responseBody = await response.json(); + const nonStreamUsage = extractUsageFromResponseBody(responseBody); + if (nonStreamUsage) { + appendUsageTracking({ + id: `${requestId || `req-${Date.now()}`}-${modelId}`, + timestamp: Date.now(), + modelId, + baseUrl: usageBaseUrl, + requestId: requestId || "unknown", + ...nonStreamUsage, + }); + } res.writeHead(response.status, { "Content-Type": "application/json", });