codeep 3.3.1 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -85,6 +85,10 @@ export interface StatsCache {
85
85
  cacheReadTokens: number;
86
86
  cacheCreationTokens: number;
87
87
  estimatedSavingsUsd: number;
88
+ /** Rates that applied, from getCacheStats. Absent → no rate is quoted rather
89
+ * than assuming Anthropic's 0.1×, which was wrong for DeepSeek, Kimi, Qwen
90
+ * and Fable 5.1. */
91
+ cacheReadRates?: number[];
88
92
  }
89
93
  export interface PricingRow {
90
94
  model: string;
@@ -8,6 +8,7 @@
8
8
  * coverage.
9
9
  */
10
10
  import { isFlatFeeProvider } from '../../config/providers.js';
11
+ import { formatCacheReadRates } from '../../utils/tokenTracker.js';
11
12
  /** Snippet window: chars of context before / after the match. */
12
13
  export const SEARCH_SNIPPET_BEFORE = 30;
13
14
  export const SEARCH_SNIPPET_AFTER = 50;
@@ -166,7 +167,7 @@ export function formatStatsReport(args) {
166
167
  }
167
168
  if (cache.cacheReadTokens > 0 || cache.cacheCreationTokens > 0) {
168
169
  lines.push('', '### Prompt caching');
169
- lines.push(`Cache reads: ${fmt(cache.cacheReadTokens)} tokens (billed at 0.1× input rate)`);
170
+ lines.push(`Cache reads: ${fmt(cache.cacheReadTokens)} tokens${formatCacheReadRates(cache.cacheReadRates ?? [])}`);
170
171
  if (cache.cacheCreationTokens > 0) {
171
172
  lines.push(`Cache writes: ${fmt(cache.cacheCreationTokens)} tokens (billed at 1.25× input rate)`);
172
173
  }
@@ -93,6 +93,21 @@ export interface ProviderCostBreakdown {
93
93
  cacheReadTokens: number;
94
94
  estimatedCost: number;
95
95
  }
96
+ /**
97
+ * What a cached prompt token costs, as a fraction of the model's input rate.
98
+ * Model first (a property of the model), then provider, then the default.
99
+ *
100
+ * One lookup for everything that needs it. Cost used this chain while savings
101
+ * hardcoded 0.1, so every provider priced differently from Anthropic had a
102
+ * cost and a "saved" figure that disagreed with each other.
103
+ */
104
+ export declare function cacheReadRateFor(model: string, provider: string | undefined): number;
105
+ /**
106
+ * The rate note for a report. One rate reads as "0.02×"; a session mixing
107
+ * providers reads as a range, because any single number there would be wrong
108
+ * for part of it.
109
+ */
110
+ export declare function formatCacheReadRates(rates: readonly number[]): string;
96
111
  /**
97
112
  * Get cost breakdown grouped by provider/model.
98
113
  *
@@ -120,6 +135,9 @@ export interface CacheStats {
120
135
  /** True when EVERY cached token came from a flat-fee plan — there is no
121
136
  * metered spend to have saved against. */
122
137
  isEntirelyFlatFeeCache: boolean;
138
+ /** The read rate of each metered record that read from cache, so a report
139
+ * can state the rate that actually applied instead of assuming 0.1×. */
140
+ cacheReadRates: number[];
123
141
  }
124
142
  export declare function getCacheStats(): CacheStats;
125
143
  /**
@@ -127,9 +127,10 @@ const MODEL_PRICING = {
127
127
  // rate, so it carries PEAK — an over-estimate by design. The previous rows
128
128
  // (0.435/0.87 and 0.14/0.28) were two to four and a half times below today's
129
129
  // peak and so under-reported, which is the one direction this table must not
130
- // err in. Cache hits cost 2% of a miss, but DeepSeek reports them as
131
- // `prompt_cache_hit_tokens`, which this tracker does not read — so every input
132
- // token bills at the miss rate: an over-estimate again, not a gap.
130
+ // err in. Cache hits are read (DeepSeek reports them both nested as
131
+ // `prompt_tokens_details.cached_tokens` and top-level as
132
+ // `prompt_cache_hit_tokens`) and billed at DeepSeek's own hit rate — see
133
+ // CACHE_READ_RATE below.
133
134
  'deepseek-flash': { inputPer1M: 0.30, outputPer1M: 1.20 },
134
135
  // Retired V4 Flash is served by V4.1 Flash and billed at its price.
135
136
  'deepseek-v4-flash': { inputPer1M: 0.30, outputPer1M: 1.20 },
@@ -250,8 +251,12 @@ export function extractOpenAIUsage(data) {
250
251
  // later. Reading only the nested form zeroed every Kimi cache hit, so the
251
252
  // cached portion of a run billed at the full cache-miss rate — five times
252
253
  // what it costs — with nothing anywhere to say so.
254
+ // DeepSeek sends the same number twice — nested `cached_tokens` and
255
+ // top-level `prompt_cache_hit_tokens` — so the nested read already covers
256
+ // it; the top-level field is a last resort, never added to the other.
253
257
  const nested = data.usage.prompt_tokens_details?.cached_tokens;
254
- const cached = (typeof nested === 'number' ? nested : data.usage.cached_tokens) || 0;
258
+ const topLevel = data.usage.cached_tokens ?? data.usage.prompt_cache_hit_tokens;
259
+ const cached = (typeof nested === 'number' ? nested : topLevel) || 0;
255
260
  return {
256
261
  promptTokens: data.usage.prompt_tokens || 0,
257
262
  completionTokens: data.usage.completion_tokens || 0,
@@ -301,8 +306,16 @@ export function extractAnthropicUsage(data) {
301
306
  */
302
307
  const MODEL_CACHE_READ_RATE = {
303
308
  'claude-fable-5-1': 0.025,
309
+ // V4 Pro's own ratio: $0.044 hit against $1.32 miss (peak; off-peak halves
310
+ // both, so the ratio holds). Historical — V4 Pro routes to V4.1 Flash from
311
+ // 2026-09-14 and configs holding it are migrated.
312
+ 'deepseek-v4-pro': 0.044 / 1.32,
304
313
  };
305
314
  const CACHE_READ_RATE = {
315
+ // V4.1 Flash: $0.006 hit against $0.30 miss at peak, $0.003 against $0.15
316
+ // off-peak — 0.02 either way. Without an entry DeepSeek fell to the 0.1
317
+ // default and every cached token billed at five times its price.
318
+ 'deepseek': 0.02,
306
319
  'kimi': 0.2,
307
320
  'kimi-api': 0.2,
308
321
  'qwen': 0.2,
@@ -313,6 +326,33 @@ const CACHE_READ_RATE = {
313
326
  };
314
327
  /** Anthropic's ratio, and the safest guess for a provider we have not priced. */
315
328
  const DEFAULT_CACHE_READ_RATE = 0.1;
329
+ /**
330
+ * What a cached prompt token costs, as a fraction of the model's input rate.
331
+ * Model first (a property of the model), then provider, then the default.
332
+ *
333
+ * One lookup for everything that needs it. Cost used this chain while savings
334
+ * hardcoded 0.1, so every provider priced differently from Anthropic had a
335
+ * cost and a "saved" figure that disagreed with each other.
336
+ */
337
+ export function cacheReadRateFor(model, provider) {
338
+ return MODEL_CACHE_READ_RATE[model]
339
+ ?? CACHE_READ_RATE[provider?.trim().toLowerCase() ?? '']
340
+ ?? DEFAULT_CACHE_READ_RATE;
341
+ }
342
+ /**
343
+ * The rate note for a report. One rate reads as "0.02×"; a session mixing
344
+ * providers reads as a range, because any single number there would be wrong
345
+ * for part of it.
346
+ */
347
+ export function formatCacheReadRates(rates) {
348
+ const fmt = (r) => `${Number(r.toFixed(3))}×`;
349
+ const distinct = [...new Set(rates.map(r => Number(r.toFixed(4))))].sort((a, b) => a - b);
350
+ if (distinct.length === 0)
351
+ return '';
352
+ if (distinct.length === 1)
353
+ return ` (billed at ${fmt(distinct[0])} input rate)`;
354
+ return ` (billed at ${fmt(distinct[0])}–${fmt(distinct[distinct.length - 1])} input rate, by model)`;
355
+ }
316
356
  /**
317
357
  * Get cost breakdown grouped by provider/model.
318
358
  *
@@ -346,9 +386,7 @@ export function getCostBreakdown(startIndex = 0) {
346
386
  // prompt tokens bill at the standard 1.0× rate.
347
387
  const cacheCreate = record.cacheCreationTokens ?? 0;
348
388
  const cacheRead = record.cacheReadTokens ?? 0;
349
- const cacheReadRate = MODEL_CACHE_READ_RATE[record.model]
350
- ?? CACHE_READ_RATE[record.provider?.trim().toLowerCase()]
351
- ?? DEFAULT_CACHE_READ_RATE;
389
+ const cacheReadRate = cacheReadRateFor(record.model, record.provider);
352
390
  const uncachedPrompt = Math.max(0, record.promptTokens - cacheCreate - cacheRead);
353
391
  existing.estimatedCost +=
354
392
  (uncachedPrompt / 1_000_000) * pricing.inputPer1M
@@ -367,6 +405,7 @@ export function getCacheStats() {
367
405
  let savings = 0;
368
406
  let flatFeeCached = 0;
369
407
  let meteredCached = 0;
408
+ const readRates = [];
370
409
  for (const record of currentRecords()) {
371
410
  const cached = (record.cacheCreationTokens ?? 0) + (record.cacheReadTokens ?? 0);
372
411
  cacheCreate += record.cacheCreationTokens ?? 0;
@@ -380,11 +419,16 @@ export function getCacheStats() {
380
419
  }
381
420
  meteredCached += cached;
382
421
  // Savings = what cache-read tokens would have cost at full input rate,
383
- // minus what they actually cost at 0.1×. (Cache creation is a slight
384
- // *penalty* of 0.25× — netted in for honest reporting.)
422
+ // minus what they cost at the model's own read rate. This hardcoded 0.9
423
+ // (a 0.1 read) for every provider, so Kimi and Qwen (0.2) over-reported
424
+ // savings while DeepSeek (0.02) and Fable 5.1 (0.025) under-reported them.
425
+ // (Cache creation is a slight *penalty* of 0.25× — netted in.)
385
426
  const pricing = MODEL_PRICING[record.model];
386
427
  if (pricing) {
387
- const cReadSaved = ((record.cacheReadTokens ?? 0) / 1_000_000) * pricing.inputPer1M * 0.9;
428
+ const readRate = cacheReadRateFor(record.model, record.provider);
429
+ if ((record.cacheReadTokens ?? 0) > 0)
430
+ readRates.push(readRate);
431
+ const cReadSaved = ((record.cacheReadTokens ?? 0) / 1_000_000) * pricing.inputPer1M * (1 - readRate);
388
432
  const cCreateCost = ((record.cacheCreationTokens ?? 0) / 1_000_000) * pricing.inputPer1M * 0.25;
389
433
  savings += cReadSaved - cCreateCost;
390
434
  }
@@ -395,6 +439,7 @@ export function getCacheStats() {
395
439
  estimatedSavingsUsd: Math.max(0, savings),
396
440
  hasFlatFeeCacheUsage: flatFeeCached > 0,
397
441
  isEntirelyFlatFeeCache: flatFeeCached > 0 && meteredCached === 0,
442
+ cacheReadRates: readRates,
398
443
  };
399
444
  }
400
445
  /**
@@ -506,7 +551,7 @@ export function formatCostReport() {
506
551
  // The billing multipliers only describe a metered account. On a plan
507
552
  // nothing is billed per token, so quoting a rate there would be as invented
508
553
  // as the per-model prices this report already refuses to show.
509
- const readNote = cache.isEntirelyFlatFeeCache ? '' : ' (billed at 0.1× input rate)';
554
+ const readNote = cache.isEntirelyFlatFeeCache ? '' : formatCacheReadRates(cache.cacheReadRates);
510
555
  const writeNote = cache.isEntirelyFlatFeeCache ? '' : ' (billed at 1.25× input rate)';
511
556
  lines.push(`**Cache reads:** ${formatTokenCount(cache.cacheReadTokens)} tokens${readNote}`);
512
557
  if (cache.cacheCreationTokens > 0) {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "3.3.1";
1
+ export declare const VERSION = "3.3.2";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
2
2
  // Baked from package.json at build time so the bun-compiled binary reports
3
3
  // the right version (it has no package.json on disk to read at runtime).
4
- export const VERSION = '3.3.1';
4
+ export const VERSION = '3.3.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",