Browse Source

feat(fallback): add ledger-only mode support

ding113 1 week ago
parent
commit
31371b20ba
3 changed files with 573 additions and 4 deletions
  1. 26 0
      src/lib/ledger-fallback.ts
  2. 299 3
      src/repository/message.ts
  3. 248 1
      src/repository/usage-logs.ts

+ 26 - 0
src/lib/ledger-fallback.ts

@@ -0,0 +1,26 @@
+import "server-only";
+
+import { sql } from "drizzle-orm";
+import { db } from "@/drizzle/db";
+
+let cachedResult: boolean | null = null;
+let cacheExpiry = 0;
+
+export async function isLedgerOnlyMode(): Promise<boolean> {
+  const now = Date.now();
+  if (cachedResult !== null && now < cacheExpiry) return cachedResult;
+
+  try {
+    const result = await db.execute(
+      sql`SELECT EXISTS(SELECT 1 FROM message_request LIMIT 1) AS has_data`
+    );
+    const hasData = (result as unknown as Array<{ has_data: boolean }>)[0]?.has_data ?? false;
+    cachedResult = !hasData;
+    cacheExpiry = now + 60_000;
+    return cachedResult;
+  } catch {
+    cachedResult = true;
+    cacheExpiry = now + 60_000;
+    return true;
+  }
+}

+ 299 - 3
src/repository/message.ts

@@ -4,6 +4,7 @@ import { and, asc, desc, eq, gt, inArray, isNull, lt, sql } from "drizzle-orm";
 import { db } from "@/drizzle/db";
 import { keys as keysTable, messageRequest, providers, usageLedger, users } from "@/drizzle/schema";
 import { getEnvConfig } from "@/lib/config/env.schema";
+import { isLedgerOnlyMode } from "@/lib/ledger-fallback";
 import { formatCostForStorage } from "@/lib/utils/currency";
 import type { CreateMessageRequestData, MessageRequest, ProviderChainItem } from "@/types/message";
 import type { SpecialSetting } from "@/types/special-settings";
@@ -232,6 +233,124 @@ export async function findLatestMessageRequestByKey(key: string): Promise<Messag
   return toMessageRequest(result);
 }
 
+export async function findMessageRequestById(id: number): Promise<MessageRequest | null> {
+  const [result] = await db
+    .select({
+      id: messageRequest.id,
+      providerId: messageRequest.providerId,
+      userId: messageRequest.userId,
+      key: messageRequest.key,
+      model: messageRequest.model,
+      originalModel: messageRequest.originalModel,
+      durationMs: messageRequest.durationMs,
+      ttfbMs: messageRequest.ttfbMs,
+      costUsd: messageRequest.costUsd,
+      costMultiplier: messageRequest.costMultiplier,
+      sessionId: messageRequest.sessionId,
+      userAgent: messageRequest.userAgent,
+      endpoint: messageRequest.endpoint,
+      messagesCount: messageRequest.messagesCount,
+      statusCode: messageRequest.statusCode,
+      inputTokens: messageRequest.inputTokens,
+      outputTokens: messageRequest.outputTokens,
+      cacheCreationInputTokens: messageRequest.cacheCreationInputTokens,
+      cacheReadInputTokens: messageRequest.cacheReadInputTokens,
+      cacheCreation5mInputTokens: messageRequest.cacheCreation5mInputTokens,
+      cacheCreation1hInputTokens: messageRequest.cacheCreation1hInputTokens,
+      cacheTtlApplied: messageRequest.cacheTtlApplied,
+      errorMessage: messageRequest.errorMessage,
+      providerChain: messageRequest.providerChain,
+      blockedBy: messageRequest.blockedBy,
+      blockedReason: messageRequest.blockedReason,
+      context1mApplied: messageRequest.context1mApplied,
+      swapCacheTtlApplied: messageRequest.swapCacheTtlApplied,
+      specialSettings: messageRequest.specialSettings,
+      createdAt: messageRequest.createdAt,
+      updatedAt: messageRequest.updatedAt,
+      deletedAt: messageRequest.deletedAt,
+    })
+    .from(messageRequest)
+    .where(and(eq(messageRequest.id, id), isNull(messageRequest.deletedAt)))
+    .limit(1);
+
+  if (result) {
+    return toMessageRequest(result);
+  }
+
+  if (!(await isLedgerOnlyMode())) {
+    return null;
+  }
+
+  const [ledgerRow] = await db
+    .select({
+      requestId: usageLedger.requestId,
+      finalProviderId: usageLedger.finalProviderId,
+      userId: usageLedger.userId,
+      key: usageLedger.key,
+      model: usageLedger.model,
+      originalModel: usageLedger.originalModel,
+      endpoint: usageLedger.endpoint,
+      statusCode: usageLedger.statusCode,
+      costUsd: usageLedger.costUsd,
+      costMultiplier: usageLedger.costMultiplier,
+      inputTokens: usageLedger.inputTokens,
+      outputTokens: usageLedger.outputTokens,
+      cacheCreationInputTokens: usageLedger.cacheCreationInputTokens,
+      cacheReadInputTokens: usageLedger.cacheReadInputTokens,
+      cacheCreation5mInputTokens: usageLedger.cacheCreation5mInputTokens,
+      cacheCreation1hInputTokens: usageLedger.cacheCreation1hInputTokens,
+      cacheTtlApplied: usageLedger.cacheTtlApplied,
+      context1mApplied: usageLedger.context1mApplied,
+      swapCacheTtlApplied: usageLedger.swapCacheTtlApplied,
+      durationMs: usageLedger.durationMs,
+      ttfbMs: usageLedger.ttfbMs,
+      sessionId: usageLedger.sessionId,
+      createdAt: usageLedger.createdAt,
+    })
+    .from(usageLedger)
+    .where(and(eq(usageLedger.requestId, id), LEDGER_BILLING_CONDITION))
+    .limit(1);
+
+  if (!ledgerRow) {
+    return null;
+  }
+
+  return toMessageRequest({
+    id: ledgerRow.requestId,
+    providerId: ledgerRow.finalProviderId,
+    userId: ledgerRow.userId,
+    key: ledgerRow.key,
+    model: ledgerRow.model,
+    originalModel: ledgerRow.originalModel,
+    durationMs: ledgerRow.durationMs,
+    ttfbMs: ledgerRow.ttfbMs,
+    costUsd: ledgerRow.costUsd,
+    costMultiplier: ledgerRow.costMultiplier,
+    sessionId: ledgerRow.sessionId,
+    userAgent: null,
+    endpoint: ledgerRow.endpoint,
+    messagesCount: null,
+    statusCode: ledgerRow.statusCode,
+    inputTokens: ledgerRow.inputTokens,
+    outputTokens: ledgerRow.outputTokens,
+    cacheCreationInputTokens: ledgerRow.cacheCreationInputTokens,
+    cacheReadInputTokens: ledgerRow.cacheReadInputTokens,
+    cacheCreation5mInputTokens: ledgerRow.cacheCreation5mInputTokens,
+    cacheCreation1hInputTokens: ledgerRow.cacheCreation1hInputTokens,
+    cacheTtlApplied: ledgerRow.cacheTtlApplied,
+    errorMessage: null,
+    providerChain: null,
+    blockedBy: null,
+    blockedReason: null,
+    context1mApplied: ledgerRow.context1mApplied,
+    swapCacheTtlApplied: ledgerRow.swapCacheTtlApplied,
+    specialSettings: null,
+    createdAt: ledgerRow.createdAt,
+    updatedAt: ledgerRow.createdAt,
+    deletedAt: null,
+  });
+}
+
 /**
  * 根据 session ID 查询消息请求记录(用于获取完整元数据)
  * 返回该 session 的最后一条记录(最新的)
@@ -274,8 +393,83 @@ export async function findMessageRequestBySessionId(
     .orderBy(desc(messageRequest.createdAt))
     .limit(1);
 
-  if (!result) return null;
-  return toMessageRequest(result);
+  if (result) {
+    return toMessageRequest(result);
+  }
+
+  if (!(await isLedgerOnlyMode())) {
+    return null;
+  }
+
+  const [ledgerRow] = await db
+    .select({
+      requestId: usageLedger.requestId,
+      finalProviderId: usageLedger.finalProviderId,
+      userId: usageLedger.userId,
+      key: usageLedger.key,
+      model: usageLedger.model,
+      originalModel: usageLedger.originalModel,
+      endpoint: usageLedger.endpoint,
+      statusCode: usageLedger.statusCode,
+      costUsd: usageLedger.costUsd,
+      costMultiplier: usageLedger.costMultiplier,
+      inputTokens: usageLedger.inputTokens,
+      outputTokens: usageLedger.outputTokens,
+      cacheCreationInputTokens: usageLedger.cacheCreationInputTokens,
+      cacheReadInputTokens: usageLedger.cacheReadInputTokens,
+      cacheCreation5mInputTokens: usageLedger.cacheCreation5mInputTokens,
+      cacheCreation1hInputTokens: usageLedger.cacheCreation1hInputTokens,
+      cacheTtlApplied: usageLedger.cacheTtlApplied,
+      context1mApplied: usageLedger.context1mApplied,
+      swapCacheTtlApplied: usageLedger.swapCacheTtlApplied,
+      durationMs: usageLedger.durationMs,
+      ttfbMs: usageLedger.ttfbMs,
+      sessionId: usageLedger.sessionId,
+      createdAt: usageLedger.createdAt,
+    })
+    .from(usageLedger)
+    .where(and(eq(usageLedger.sessionId, sessionId), LEDGER_BILLING_CONDITION))
+    .orderBy(desc(usageLedger.createdAt), desc(usageLedger.requestId))
+    .limit(1);
+
+  if (!ledgerRow) {
+    return null;
+  }
+
+  return toMessageRequest({
+    id: ledgerRow.requestId,
+    providerId: ledgerRow.finalProviderId,
+    userId: ledgerRow.userId,
+    key: ledgerRow.key,
+    model: ledgerRow.model,
+    originalModel: ledgerRow.originalModel,
+    durationMs: ledgerRow.durationMs,
+    ttfbMs: ledgerRow.ttfbMs,
+    costUsd: ledgerRow.costUsd,
+    costMultiplier: ledgerRow.costMultiplier,
+    sessionId: ledgerRow.sessionId,
+    userAgent: null,
+    endpoint: ledgerRow.endpoint,
+    messagesCount: null,
+    statusCode: ledgerRow.statusCode,
+    inputTokens: ledgerRow.inputTokens,
+    outputTokens: ledgerRow.outputTokens,
+    cacheCreationInputTokens: ledgerRow.cacheCreationInputTokens,
+    cacheReadInputTokens: ledgerRow.cacheReadInputTokens,
+    cacheCreation5mInputTokens: ledgerRow.cacheCreation5mInputTokens,
+    cacheCreation1hInputTokens: ledgerRow.cacheCreation1hInputTokens,
+    cacheTtlApplied: ledgerRow.cacheTtlApplied,
+    errorMessage: null,
+    providerChain: null,
+    blockedBy: null,
+    blockedReason: null,
+    context1mApplied: ledgerRow.context1mApplied,
+    swapCacheTtlApplied: ledgerRow.swapCacheTtlApplied,
+    specialSettings: null,
+    createdAt: ledgerRow.createdAt,
+    updatedAt: ledgerRow.createdAt,
+    deletedAt: null,
+  });
 }
 
 /**
@@ -766,7 +960,109 @@ export async function findUsageLogs(params: {
 
   const logs = results.map(toMessageRequest);
 
-  return { logs, total };
+  if (logs.length > 0) {
+    return { logs, total };
+  }
+
+  if (!(await isLedgerOnlyMode())) {
+    return { logs, total };
+  }
+
+  const ledgerConditions = [LEDGER_BILLING_CONDITION];
+
+  if (userId !== undefined) {
+    ledgerConditions.push(eq(usageLedger.userId, userId));
+  }
+
+  if (startDate) {
+    ledgerConditions.push(sql`${usageLedger.createdAt} >= ${startDate}`);
+  }
+
+  if (endDate) {
+    ledgerConditions.push(sql`${usageLedger.createdAt} <= ${endDate}`);
+  }
+
+  if (model) {
+    ledgerConditions.push(eq(usageLedger.model, model));
+  }
+
+  const [ledgerCountResult] = await db
+    .select({ count: sql<number>`count(*)::int` })
+    .from(usageLedger)
+    .where(and(...ledgerConditions));
+
+  const ledgerTotal = ledgerCountResult?.count ?? 0;
+
+  const ledgerResults = await db
+    .select({
+      requestId: usageLedger.requestId,
+      finalProviderId: usageLedger.finalProviderId,
+      userId: usageLedger.userId,
+      key: usageLedger.key,
+      model: usageLedger.model,
+      originalModel: usageLedger.originalModel,
+      endpoint: usageLedger.endpoint,
+      statusCode: usageLedger.statusCode,
+      costUsd: usageLedger.costUsd,
+      costMultiplier: usageLedger.costMultiplier,
+      inputTokens: usageLedger.inputTokens,
+      outputTokens: usageLedger.outputTokens,
+      cacheCreationInputTokens: usageLedger.cacheCreationInputTokens,
+      cacheReadInputTokens: usageLedger.cacheReadInputTokens,
+      cacheCreation5mInputTokens: usageLedger.cacheCreation5mInputTokens,
+      cacheCreation1hInputTokens: usageLedger.cacheCreation1hInputTokens,
+      cacheTtlApplied: usageLedger.cacheTtlApplied,
+      context1mApplied: usageLedger.context1mApplied,
+      swapCacheTtlApplied: usageLedger.swapCacheTtlApplied,
+      durationMs: usageLedger.durationMs,
+      ttfbMs: usageLedger.ttfbMs,
+      sessionId: usageLedger.sessionId,
+      createdAt: usageLedger.createdAt,
+    })
+    .from(usageLedger)
+    .where(and(...ledgerConditions))
+    .orderBy(desc(usageLedger.createdAt), desc(usageLedger.requestId))
+    .limit(pageSize)
+    .offset(offset);
+
+  const ledgerLogs = ledgerResults.map((row) =>
+    toMessageRequest({
+      id: row.requestId,
+      providerId: row.finalProviderId,
+      userId: row.userId,
+      key: row.key,
+      model: row.model,
+      originalModel: row.originalModel,
+      durationMs: row.durationMs,
+      ttfbMs: row.ttfbMs,
+      costUsd: row.costUsd,
+      costMultiplier: row.costMultiplier,
+      sessionId: row.sessionId,
+      userAgent: null,
+      endpoint: row.endpoint,
+      messagesCount: null,
+      statusCode: row.statusCode,
+      inputTokens: row.inputTokens,
+      outputTokens: row.outputTokens,
+      cacheCreationInputTokens: row.cacheCreationInputTokens,
+      cacheReadInputTokens: row.cacheReadInputTokens,
+      cacheCreation5mInputTokens: row.cacheCreation5mInputTokens,
+      cacheCreation1hInputTokens: row.cacheCreation1hInputTokens,
+      cacheTtlApplied: row.cacheTtlApplied,
+      errorMessage: null,
+      providerChain: null,
+      blockedBy: null,
+      blockedReason: null,
+      context1mApplied: row.context1mApplied,
+      swapCacheTtlApplied: row.swapCacheTtlApplied,
+      specialSettings: null,
+      createdAt: row.createdAt,
+      updatedAt: row.createdAt,
+      deletedAt: null,
+    })
+  );
+
+  return { logs: ledgerLogs, total: ledgerTotal };
 }
 
 /**

+ 248 - 1
src/repository/usage-logs.ts

@@ -4,6 +4,7 @@ import { and, desc, eq, gte, isNull, lt, sql } from "drizzle-orm";
 import { db } from "@/drizzle/db";
 import { keys as keysTable, messageRequest, providers, usageLedger, users } from "@/drizzle/schema";
 import { TTLMap } from "@/lib/cache/ttl-map";
+import { isLedgerOnlyMode } from "@/lib/ledger-fallback";
 import { buildUnifiedSpecialSettings } from "@/lib/utils/special-settings";
 import type { ProviderChainItem } from "@/types/message";
 import type { SpecialSetting } from "@/types/special-settings";
@@ -234,7 +235,156 @@ export async function findUsageLogsBatch(
     };
   });
 
-  return { logs, nextCursor, hasMore };
+  if (logs.length > 0) {
+    return { logs, nextCursor, hasMore };
+  }
+
+  if (!(await isLedgerOnlyMode())) {
+    return { logs, nextCursor, hasMore };
+  }
+
+  if (filters.minRetryCount !== undefined && filters.minRetryCount > 0) {
+    return { logs: [], nextCursor: null, hasMore: false };
+  }
+
+  const ledgerConditions = [LEDGER_BILLING_CONDITION];
+
+  if (userId !== undefined) {
+    ledgerConditions.push(eq(usageLedger.userId, userId));
+  }
+
+  if (keyId !== undefined) {
+    ledgerConditions.push(eq(keysTable.id, keyId));
+  }
+
+  if (providerId !== undefined) {
+    ledgerConditions.push(eq(usageLedger.finalProviderId, providerId));
+  }
+
+  const trimmedSessionId = filters.sessionId?.trim();
+  if (trimmedSessionId) {
+    ledgerConditions.push(eq(usageLedger.sessionId, trimmedSessionId));
+  }
+
+  if (filters.startTime) {
+    ledgerConditions.push(gte(usageLedger.createdAt, new Date(filters.startTime)));
+  }
+
+  if (filters.endTime) {
+    ledgerConditions.push(lt(usageLedger.createdAt, new Date(filters.endTime)));
+  }
+
+  if (filters.statusCode !== undefined) {
+    ledgerConditions.push(eq(usageLedger.statusCode, filters.statusCode));
+  } else if (filters.excludeStatusCode200) {
+    ledgerConditions.push(
+      sql`(${usageLedger.statusCode} IS NULL OR ${usageLedger.statusCode} <> 200)`
+    );
+  }
+
+  if (filters.model) {
+    ledgerConditions.push(eq(usageLedger.model, filters.model));
+  }
+
+  if (filters.endpoint) {
+    ledgerConditions.push(eq(usageLedger.endpoint, filters.endpoint));
+  }
+
+  if (cursor) {
+    ledgerConditions.push(
+      sql`(${usageLedger.createdAt}, ${usageLedger.requestId}) < (${cursor.createdAt}::timestamptz, ${cursor.id})`
+    );
+  }
+
+  const ledgerResults = await db
+    .select({
+      id: usageLedger.requestId,
+      createdAt: usageLedger.createdAt,
+      createdAtRaw: sql<string>`to_char(${usageLedger.createdAt} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`,
+      sessionId: usageLedger.sessionId,
+      userId: usageLedger.userId,
+      userName: users.name,
+      key: usageLedger.key,
+      keyName: keysTable.name,
+      providerName: providers.name,
+      model: usageLedger.model,
+      originalModel: usageLedger.originalModel,
+      endpoint: usageLedger.endpoint,
+      statusCode: usageLedger.statusCode,
+      inputTokens: usageLedger.inputTokens,
+      outputTokens: usageLedger.outputTokens,
+      cacheCreationInputTokens: usageLedger.cacheCreationInputTokens,
+      cacheReadInputTokens: usageLedger.cacheReadInputTokens,
+      cacheCreation5mInputTokens: usageLedger.cacheCreation5mInputTokens,
+      cacheCreation1hInputTokens: usageLedger.cacheCreation1hInputTokens,
+      cacheTtlApplied: usageLedger.cacheTtlApplied,
+      costUsd: usageLedger.costUsd,
+      costMultiplier: usageLedger.costMultiplier,
+      durationMs: usageLedger.durationMs,
+      ttfbMs: usageLedger.ttfbMs,
+      context1mApplied: usageLedger.context1mApplied,
+      swapCacheTtlApplied: usageLedger.swapCacheTtlApplied,
+    })
+    .from(usageLedger)
+    .leftJoin(users, eq(usageLedger.userId, users.id))
+    .leftJoin(keysTable, eq(usageLedger.key, keysTable.key))
+    .leftJoin(providers, eq(usageLedger.finalProviderId, providers.id))
+    .where(and(...ledgerConditions))
+    .orderBy(desc(usageLedger.createdAt), desc(usageLedger.requestId))
+    .limit(fetchLimit);
+
+  const ledgerHasMore = ledgerResults.length > limit;
+  const ledgerRowsToReturn = ledgerHasMore ? ledgerResults.slice(0, limit) : ledgerResults;
+  const ledgerLastLog = ledgerRowsToReturn[ledgerRowsToReturn.length - 1];
+  const ledgerNextCursor =
+    ledgerHasMore && ledgerLastLog?.createdAtRaw
+      ? { createdAt: ledgerLastLog.createdAtRaw, id: ledgerLastLog.id }
+      : null;
+
+  const fallbackLogs: UsageLogRow[] = ledgerRowsToReturn.map((row) => {
+    const totalRowTokens =
+      (row.inputTokens ?? 0) +
+      (row.outputTokens ?? 0) +
+      (row.cacheCreationInputTokens ?? 0) +
+      (row.cacheReadInputTokens ?? 0);
+
+    return {
+      id: row.id,
+      createdAt: row.createdAt,
+      sessionId: row.sessionId,
+      requestSequence: null,
+      userName: row.userName ?? `User #${row.userId}`,
+      keyName: row.keyName ?? row.key,
+      providerName: row.providerName,
+      model: row.model,
+      originalModel: row.originalModel,
+      endpoint: row.endpoint,
+      statusCode: row.statusCode,
+      inputTokens: row.inputTokens,
+      outputTokens: row.outputTokens,
+      cacheCreationInputTokens: row.cacheCreationInputTokens,
+      cacheReadInputTokens: row.cacheReadInputTokens,
+      cacheCreation5mInputTokens: row.cacheCreation5mInputTokens,
+      cacheCreation1hInputTokens: row.cacheCreation1hInputTokens,
+      cacheTtlApplied: row.cacheTtlApplied,
+      totalTokens: totalRowTokens,
+      costUsd: row.costUsd?.toString() ?? null,
+      costMultiplier: row.costMultiplier?.toString() ?? null,
+      durationMs: row.durationMs,
+      ttfbMs: row.ttfbMs,
+      errorMessage: null,
+      providerChain: null,
+      blockedBy: null,
+      blockedReason: null,
+      userAgent: null,
+      messagesCount: null,
+      context1mApplied: row.context1mApplied ?? null,
+      swapCacheTtlApplied: row.swapCacheTtlApplied ?? null,
+      specialSettings: null,
+    };
+  });
+
+  return { logs: fallbackLogs, nextCursor: ledgerNextCursor, hasMore: ledgerHasMore };
 }
 
 interface UsageLogSlimFilters {
@@ -333,6 +483,103 @@ export async function findUsageLogsForKeySlim(
   const hasMore = results.length > safePageSize;
   const pageRows = hasMore ? results.slice(0, safePageSize) : results;
 
+  if (pageRows.length === 0 && (await isLedgerOnlyMode())) {
+    if (filters.minRetryCount !== undefined && filters.minRetryCount > 0) {
+      return { logs: [], total: 0 };
+    }
+
+    const ledgerConditions = [LEDGER_BILLING_CONDITION, eq(usageLedger.key, keyString)];
+
+    const trimmedSessionId = filters.sessionId?.trim();
+    if (trimmedSessionId) {
+      ledgerConditions.push(eq(usageLedger.sessionId, trimmedSessionId));
+    }
+
+    if (filters.startTime) {
+      ledgerConditions.push(gte(usageLedger.createdAt, new Date(filters.startTime)));
+    }
+
+    if (filters.endTime) {
+      ledgerConditions.push(lt(usageLedger.createdAt, new Date(filters.endTime)));
+    }
+
+    if (filters.statusCode !== undefined) {
+      ledgerConditions.push(eq(usageLedger.statusCode, filters.statusCode));
+    } else if (filters.excludeStatusCode200) {
+      ledgerConditions.push(
+        sql`(${usageLedger.statusCode} IS NULL OR ${usageLedger.statusCode} <> 200)`
+      );
+    }
+
+    if (filters.model) {
+      ledgerConditions.push(eq(usageLedger.model, filters.model));
+    }
+
+    if (filters.endpoint) {
+      ledgerConditions.push(eq(usageLedger.endpoint, filters.endpoint));
+    }
+
+    const ledgerResults = await db
+      .select({
+        id: usageLedger.requestId,
+        createdAt: usageLedger.createdAt,
+        model: usageLedger.model,
+        originalModel: usageLedger.originalModel,
+        endpoint: usageLedger.endpoint,
+        statusCode: usageLedger.statusCode,
+        inputTokens: usageLedger.inputTokens,
+        outputTokens: usageLedger.outputTokens,
+        costUsd: usageLedger.costUsd,
+        durationMs: usageLedger.durationMs,
+        cacheCreationInputTokens: usageLedger.cacheCreationInputTokens,
+        cacheReadInputTokens: usageLedger.cacheReadInputTokens,
+        cacheCreation5mInputTokens: usageLedger.cacheCreation5mInputTokens,
+        cacheCreation1hInputTokens: usageLedger.cacheCreation1hInputTokens,
+        cacheTtlApplied: usageLedger.cacheTtlApplied,
+      })
+      .from(usageLedger)
+      .where(and(...ledgerConditions))
+      .orderBy(desc(usageLedger.createdAt), desc(usageLedger.requestId))
+      .limit(safePageSize + 1)
+      .offset(offset);
+
+    const ledgerHasMore = ledgerResults.length > safePageSize;
+    const ledgerPageRows = ledgerHasMore ? ledgerResults.slice(0, safePageSize) : ledgerResults;
+
+    let ledgerTotal = offset + ledgerPageRows.length;
+
+    const cachedTotal = usageLogSlimTotalCache.get(totalCacheKey);
+    if (cachedTotal !== undefined) {
+      ledgerTotal = Math.max(cachedTotal, ledgerTotal);
+      return {
+        logs: ledgerPageRows.map((row) => ({ ...row, costUsd: row.costUsd?.toString() ?? null })),
+        total: ledgerTotal,
+      };
+    }
+
+    if (ledgerPageRows.length === 0 && offset > 0) {
+      const countResults = await db
+        .select({ totalRows: sql<number>`count(*)::double precision` })
+        .from(usageLedger)
+        .where(and(...ledgerConditions));
+      ledgerTotal = countResults[0]?.totalRows ?? 0;
+    } else if (ledgerHasMore) {
+      const countResults = await db
+        .select({ totalRows: sql<number>`count(*)::double precision` })
+        .from(usageLedger)
+        .where(and(...ledgerConditions));
+      ledgerTotal = countResults[0]?.totalRows ?? 0;
+    }
+
+    const ledgerLogs: UsageLogSlimRow[] = ledgerPageRows.map((row) => ({
+      ...row,
+      costUsd: row.costUsd?.toString() ?? null,
+    }));
+
+    usageLogSlimTotalCache.set(totalCacheKey, ledgerTotal);
+    return { logs: ledgerLogs, total: ledgerTotal };
+  }
+
   let total = offset + pageRows.length;
 
   const cachedTotal = usageLogSlimTotalCache.get(totalCacheKey);