Skip to content

Commit 1ecb2cf

Browse files
committed
feat: implement offline mode in data loader and pricing fetcher
- Add offline flag support to LoadOptions interface in data-loader - Implement offline mode in PricingFetcher using pre-fetched macro data - Add caching logic for offline pricing data to avoid repeated object creation - Return cached pricing data when offline flag is enabled - Provide graceful fallback when macro data is unavailable in offline mode - Add proper error handling for offline mode edge cases The pricing fetcher now supports both online (LiteLLM API) and offline (build-time cached) modes, enabling usage without network connectivity.
1 parent 39af140 commit 1ecb2cf

2 files changed

Lines changed: 17 additions & 7 deletions

File tree

src/data-loader.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ export type LoadOptions = {
464464
claudePath?: string; // Custom path to Claude data directory
465465
mode?: CostMode; // Cost calculation mode
466466
order?: SortOrder; // Sort order for dates
467+
offline?: boolean; // Use offline mode for pricing
467468
} & DateFilter;
468469

469470
export async function loadDailyUsageData(
@@ -487,7 +488,7 @@ export async function loadDailyUsageData(
487488
const mode = options?.mode ?? 'auto';
488489

489490
// Use PricingFetcher with using statement for automatic cleanup
490-
using fetcher = mode === 'display' ? null : new PricingFetcher();
491+
using fetcher = mode === 'display' ? null : new PricingFetcher(options?.offline);
491492

492493
// Track processed message+request combinations for deduplication
493494
const processedHashes = new Set<string>();
@@ -603,7 +604,7 @@ export async function loadSessionData(
603604
const mode = options?.mode ?? 'auto';
604605

605606
// Use PricingFetcher with using statement for automatic cleanup
606-
using fetcher = mode === 'display' ? null : new PricingFetcher();
607+
using fetcher = mode === 'display' ? null : new PricingFetcher(options?.offline);
607608

608609
// Track processed message+request combinations for deduplication
609610
const processedHashes = new Set<string>();

src/pricing-fetcher.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as v from 'valibot';
2+
import { LITELLM_PRICING_URL } from './consts.internal.ts';
23
import { logger } from './logger.ts';
4+
import { prefetchClaudePricing } from './macro.internal.ts' with { type: 'macro' };
35

4-
const ModelPricingSchema = v.object({
6+
export const ModelPricingSchema = v.object({
57
input_cost_per_token: v.optional(v.number()),
68
output_cost_per_token: v.optional(v.number()),
79
cache_creation_input_token_cost: v.optional(v.number()),
@@ -10,13 +12,13 @@ const ModelPricingSchema = v.object({
1012

1113
export type ModelPricing = v.InferOutput<typeof ModelPricingSchema>;
1214

13-
const LITELLM_PRICING_URL
14-
= 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json';
15-
1615
export class PricingFetcher implements Disposable {
1716
private cachedPricing: Map<string, ModelPricing> | null = null;
17+
private readonly offline: boolean;
1818

19-
constructor() {}
19+
constructor(offline = false) {
20+
this.offline = offline;
21+
}
2022

2123
[Symbol.dispose](): void {
2224
this.clearCache();
@@ -31,6 +33,13 @@ export class PricingFetcher implements Disposable {
3133
return this.cachedPricing;
3234
}
3335

36+
// If we're in offline mode, return pre-fetched data
37+
if (this.offline) {
38+
const pricing = new Map(Object.entries(await prefetchClaudePricing()));
39+
this.cachedPricing = pricing;
40+
return pricing;
41+
}
42+
3443
try {
3544
logger.warn('Fetching latest model pricing from LiteLLM...');
3645
const response = await fetch(LITELLM_PRICING_URL);

0 commit comments

Comments
 (0)