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
|
|
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
|
|
131
|
-
// `
|
|
132
|
-
//
|
|
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
|
|
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 =
|
|
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
|
|
384
|
-
//
|
|
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
|
|
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 ? '' :
|
|
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
|
+
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.
|
|
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.
|
|
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",
|