codebuddy-stats 1.3.5 → 1.4.0

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.
package/dist/index.js CHANGED
@@ -195,9 +195,10 @@ async function main() {
195
195
  function updateContent() {
196
196
  const t0 = prof ? performance.now() : 0;
197
197
  const width = Number(screen.width) || 80;
198
+ const estimatedRate = formatPercent(state.data.ideEstimatedCacheHitRate ?? 0.9);
198
199
  const note = state.currentSource === 'code'
199
200
  ? `针对 CodeBuddy Code < 2.20.0 版本产生的数据,由于没有请求级别的 model ID,用量是基于当前 CodeBuddy Code 设置的 model ID(${state.data.defaultModelId})计算价格的`
200
- : 'IDE usage 不包含缓存命中/写入 tokens,无法计算缓存相关价格与命中率;成本按 input/output tokens 估算';
201
+ : `IDE 的数据有缺,成本根据 Code 最近 90 天的平均缓存命中率估算(当前为 ${estimatedRate})`;
201
202
  const screenHeight = Number(screen.height) || 24;
202
203
  const contentBoxHeight = Math.max(1, screenHeight - 5); // 对应 contentBox: height = '100%-5'
203
204
  const paddingTop = Number(contentBox.padding?.top ?? 0);
@@ -12,6 +12,8 @@ import { compareByCostThenTokens } from './utils.js';
12
12
  import { loadWorkspaceMappings, resolveProjectName } from './workspace-resolver.js';
13
13
  export const BASE_DIR = getProjectsDir();
14
14
  const memoryCache = new Map();
15
+ const IDE_CACHE_HIT_RATE_LOOKBACK_DAYS = 90;
16
+ const IDE_CACHE_HIT_RATE_FALLBACK = 0.9;
15
17
  function getCacheKey(source, days) {
16
18
  return `${source}:${days ?? 'all'}`;
17
19
  }
@@ -461,6 +463,23 @@ async function loadCodeUsageData(options = {}) {
461
463
  }
462
464
  return data;
463
465
  }
466
+ async function resolveIdeEstimatedCacheHitRate() {
467
+ try {
468
+ const codeData = await loadCodeUsageData({ days: IDE_CACHE_HIT_RATE_LOOKBACK_DAYS });
469
+ const hitTokens = codeData.grandTotal.cacheHitTokens;
470
+ const missTokens = codeData.grandTotal.cacheMissTokens;
471
+ const totalTokens = hitTokens + missTokens;
472
+ if (totalTokens <= 0)
473
+ return IDE_CACHE_HIT_RATE_FALLBACK;
474
+ const rate = hitTokens / totalTokens;
475
+ if (!Number.isFinite(rate))
476
+ return IDE_CACHE_HIT_RATE_FALLBACK;
477
+ return Math.min(1, Math.max(0, rate));
478
+ }
479
+ catch {
480
+ return IDE_CACHE_HIT_RATE_FALLBACK;
481
+ }
482
+ }
464
483
  async function findIdeHistoryDirs() {
465
484
  const root = getIdeDataDir();
466
485
  const out = new Set();
@@ -565,6 +584,7 @@ async function loadIdeUsageData(options = {}) {
565
584
  const t0 = prof ? performance.now() : 0;
566
585
  const defaultModelId = await loadModelFromSettings();
567
586
  const minDate = computeMinDate(options.days);
587
+ const estimatedCacheHitRate = await resolveIdeEstimatedCacheHitRate();
568
588
  // 加载工作区映射
569
589
  const workspaceMappings = await loadWorkspaceMappings();
570
590
  const dailyData = {};
@@ -581,7 +601,7 @@ async function loadIdeUsageData(options = {}) {
581
601
  // 同进程内存缓存(不落盘):refresh 时如果 IDE 索引未变,直接复用结果
582
602
  const sortedHistoryDirs = [...historyDirs].sort();
583
603
  const ideHash = createHash('sha1');
584
- ideHash.update(`ide|defaultModelId=${defaultModelId}|minDate=${minDate ?? ''}|historyDirs=${sortedHistoryDirs.length}\n`);
604
+ ideHash.update(`ide|defaultModelId=${defaultModelId}|minDate=${minDate ?? ''}|historyDirs=${sortedHistoryDirs.length}|ideEstimatedCacheHitRate=${estimatedCacheHitRate.toFixed(6)}|lookbackDays=${IDE_CACHE_HIT_RATE_LOOKBACK_DAYS}\n`);
585
605
  const mappingKeys = [...workspaceMappings.keys()].sort();
586
606
  ideHash.update(`mappings=${mappingKeys.length}\n`);
587
607
  for (const k of mappingKeys) {
@@ -682,10 +702,16 @@ async function loadIdeUsageData(options = {}) {
682
702
  continue;
683
703
  const inferredModelId = await inferIdeModelIdForRequest(conversationDir, req, messageModelCache);
684
704
  const usedModelId = inferredModelId || defaultModelId;
705
+ // IDE 的 usage 不包含缓存 token 明细,按 CBC 最近 90 天平均命中率估算(无数据回退 90%)
706
+ const safeInput = Math.max(0, inputTokens);
707
+ const estimatedCacheHit = Math.round(safeInput * estimatedCacheHitRate);
708
+ const estimatedCacheMiss = safeInput - estimatedCacheHit;
685
709
  const rawUsage = {
686
- prompt_tokens: Math.max(0, inputTokens),
710
+ prompt_tokens: safeInput,
687
711
  completion_tokens: Math.max(0, outputTokens),
688
712
  total_tokens: Math.max(0, totalTokens),
713
+ prompt_cache_hit_tokens: estimatedCacheHit,
714
+ prompt_cache_miss_tokens: estimatedCacheMiss,
689
715
  };
690
716
  const { cost, stats } = computeUsageCost(rawUsage, usedModelId);
691
717
  const projectName = workspaceHash;
@@ -694,6 +720,8 @@ async function loadIdeUsageData(options = {}) {
694
720
  dayStats.promptTokens += stats.promptTokens;
695
721
  dayStats.completionTokens += stats.completionTokens;
696
722
  dayStats.totalTokens += stats.totalTokens;
723
+ dayStats.cacheHitTokens += stats.cacheHitTokens;
724
+ dayStats.cacheMissTokens += stats.cacheMissTokens;
697
725
  dayStats.requests += 1;
698
726
  modelTotals[usedModelId] ??= { cost: 0, tokens: 0, requests: 0 };
699
727
  modelTotals[usedModelId].cost += cost;
@@ -706,11 +734,14 @@ async function loadIdeUsageData(options = {}) {
706
734
  grandTotal.cost += cost;
707
735
  grandTotal.tokens += stats.totalTokens;
708
736
  grandTotal.requests += 1;
737
+ grandTotal.cacheHitTokens += stats.cacheHitTokens;
738
+ grandTotal.cacheMissTokens += stats.cacheMissTokens;
709
739
  }
710
740
  }
711
741
  }
712
742
  }
713
743
  const data = finalizeAnalysis(defaultModelId, dailyData, modelTotals, projectTotals, grandTotal, workspaceMappings);
744
+ data.ideEstimatedCacheHitRate = estimatedCacheHitRate;
714
745
  cacheSet('ide', options.days, { fingerprint, data });
715
746
  if (profLog) {
716
747
  const ms = performance.now() - t0;
@@ -25,12 +25,31 @@ export const MODEL_PRICING = {
25
25
  "gpt-5-nano": createPricing(0.05, 0.005, 0.4),
26
26
  "gpt-5.1-chat-latest": createPricing(1.25, 0.125, 10.0),
27
27
  "gpt-5-chat-latest": createPricing(1.25, 0.125, 10.0),
28
+ "gpt-5.3-codex": createPricing(1.75, 0.175, 14.0),
28
29
  "gpt-5.2-codex": createPricing(1.75, 0.175, 14.0),
29
30
  "gpt-5.1-codex": createPricing(1.25, 0.125, 10.0),
30
31
  "gpt-5.1-codex-max": createPricing(1.25, 0.125, 10.0),
31
32
  "gpt-5.1-codex-mini": createPricing(0.25, 0.025, 2.0),
32
33
  "gpt-5-codex": createPricing(1.25, 0.125, 10.0),
33
34
  // Claude 系列
35
+ "claude-sonnet-4.6": {
36
+ prompt: [
37
+ { limit: 200_000, pricePerMTok: 3.0 },
38
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 6.0 },
39
+ ],
40
+ completion: [
41
+ { limit: 200_000, pricePerMTok: 15.0 },
42
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 22.5 },
43
+ ],
44
+ cacheRead: [
45
+ { limit: 200_000, pricePerMTok: 0.3 },
46
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.6 },
47
+ ],
48
+ cacheWrite: [
49
+ { limit: 200_000, pricePerMTok: 6.0 },
50
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 12.0 },
51
+ ],
52
+ },
34
53
  "claude-opus-4.6": {
35
54
  prompt: [
36
55
  { limit: 200_000, pricePerMTok: 5.0 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebuddy-stats",
3
- "version": "1.3.5",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [