ccus-cli 0.2.10 → 0.2.11

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.
@@ -17,6 +17,7 @@ const promises_1 = __importDefault(require("node:fs/promises"));
17
17
  const node_path_1 = __importDefault(require("node:path"));
18
18
  const node_util_1 = require("node:util");
19
19
  const node_zlib_1 = require("node:zlib");
20
+ const api_equivalent_cost_1 = require("./api-equivalent-cost");
20
21
  const payload_1 = require("./payload");
21
22
  const time_1 = require("./time");
22
23
  const gunzipAsync = (0, node_util_1.promisify)(node_zlib_1.gunzip);
@@ -46,17 +47,48 @@ function hasWeeklyStatuslineShape(value) {
46
47
  (typeof value.sevenDayLatestUsagePct === "number" || value.sevenDayLatestUsagePct === null) &&
47
48
  (typeof value.sevenDayPeakUsagePct === "number" || value.sevenDayPeakUsagePct === null));
48
49
  }
50
+ function hasApiEquivalentCostResultShape(value) {
51
+ return (isRecord(value) &&
52
+ (value.estimatedUsd === null || (typeof value.estimatedUsd === "number" && Number.isFinite(value.estimatedUsd) && value.estimatedUsd >= 0)) &&
53
+ typeof value.pricedApiRequestCount === "number" &&
54
+ Number.isInteger(value.pricedApiRequestCount) &&
55
+ value.pricedApiRequestCount >= 0 &&
56
+ typeof value.unpricedApiRequestCount === "number" &&
57
+ Number.isInteger(value.unpricedApiRequestCount) &&
58
+ value.unpricedApiRequestCount >= 0);
59
+ }
60
+ function hasApiEquivalentCostBreakdownShape(value) {
61
+ return (isRecord(value) &&
62
+ hasApiEquivalentCostResultShape(value.claude) &&
63
+ hasApiEquivalentCostResultShape(value.codex) &&
64
+ hasApiEquivalentCostResultShape(value.total));
65
+ }
66
+ function hasPricingMetadataShape(value) {
67
+ return (isRecord(value) &&
68
+ typeof value.catalogVersion === "string" &&
69
+ value.catalogVersion.length > 0 &&
70
+ value.currency === "USD" &&
71
+ value.basis === "event-time-standard-api");
72
+ }
49
73
  function isWeeklyExportBundle(value) {
50
74
  if (!isRecord(value)) {
51
75
  return false;
52
76
  }
53
- if (typeof value.schemaVersion !== "number" || ![6, 7, 8, 9].includes(value.schemaVersion)) {
77
+ if (typeof value.schemaVersion !== "number" || ![6, 7, 8, 9, 10].includes(value.schemaVersion)) {
54
78
  return false;
55
79
  }
56
80
  if (!Array.isArray(value.rawEvents) || !Array.isArray(value.dailySummaries) || !isRecord(value.weeklySummary) || !isRecord(value.identity) || !isRecord(value.range)) {
57
81
  return false;
58
82
  }
59
- return hasWeeklyStatuslineShape(value.weeklySummary.statusline);
83
+ if (!hasWeeklyStatuslineShape(value.weeklySummary.statusline)) {
84
+ return false;
85
+ }
86
+ if (value.schemaVersion !== 10) {
87
+ return true;
88
+ }
89
+ return (hasPricingMetadataShape(value.pricing) &&
90
+ hasApiEquivalentCostBreakdownShape(value.weeklySummary.apiEquivalentCost) &&
91
+ value.dailySummaries.every((day) => isRecord(day) && hasApiEquivalentCostBreakdownShape(day.apiEquivalentCost)));
60
92
  }
61
93
  function localDateKey(date) {
62
94
  const year = date.getFullYear();
@@ -131,17 +163,14 @@ async function loadWeeklyExportBundles(inputDir) {
131
163
  }
132
164
  }
133
165
  if (invalidFiles.length > 0) {
134
- throw new Error(`Unsupported export bundle schema in files: ${invalidFiles.join(", ")}. Re-export with current ccus so aggregate receives schemaVersion 6/7/8/9 bundles.`);
166
+ throw new Error(`Unsupported export bundle schema in files: ${invalidFiles.join(", ")}. Re-export with current ccus so aggregate receives schemaVersion 6/7/8/9/10 bundles.`);
135
167
  }
136
168
  return bundles;
137
169
  }
138
170
  /**
139
- * 同一个人在多台电脑导出多个 bundle 时的合并策略。
140
- *
141
- * 累加类字段(token / 消息数 / 采样数等)怕重复计数(同一台机器重复导出、周与周重叠),
142
- * 所以按「同人同天 / 同人同周取 generatedAt 最新的那份导出 bundle」去重,不相加。
143
- * usage 是百分比快照、不是累加量,从选中那份 winner bundle 的 rawEvents 按真实时间戳重算
144
- * (peak 取 max,latest 取时间戳最新),某指标在 rawEvents 里缺失时回退到 daySummary/weeklySummary 自带值。
171
+ * 同一个人在多台电脑导出多个 bundle 时,先按同人同天和 sessionId 交集识别重复导出,
172
+ * 每组只保留最优代表;不同机器的独立代表继续累加。usage 从所有代表事件重算,
173
+ * rawEvents 缺失时回退到 daySummary 自带值,weekly 再由代表日上卷。
145
174
  */
146
175
  /** bundle 的 personKey 解析结果做一次缓存,避免反复计算。 */
147
176
  function bundlePersonKey(bundle) {
@@ -164,7 +193,7 @@ function dayDataTier(day) {
164
193
  return 0;
165
194
  }
166
195
  /**
167
- * winner 比较:数据质量等级高优先;同 tier=2 時消息数多优先(避免"仅 generatedAt 更新"的低活跃机器
196
+ * winner 比较:数据质量等级高优先;同 tier=2 时消息数多优先(避免“仅 generatedAt 更新”的低活跃机器
168
197
  * 覆盖同一天高活跃机器的数据);同 count 内 generatedAt 较新优先;最后用 filePath 做稳定 tie-break。
169
198
  */
170
199
  function isBetterCandidate(nextTier, nextMsgCount, nextApiCount, nextGeneratedAt, nextFilePath, currentTier, currentMsgCount, currentApiCount, currentGeneratedAt, currentFilePath) {
@@ -189,7 +218,7 @@ function isBetterCandidate(nextTier, nextMsgCount, nextApiCount, nextGeneratedAt
189
218
  * - 有交集的视为同机器重复导出(同一账号同一天的会话在两份 bundle 里均存在),只取最优 winner
190
219
  * - 无交集的视为不同机器的独立数据,分别保留,后续叠加
191
220
  *
192
- * sessionId 集合为空的候选(该天没有 statusline 事件)不参与交集判断,单独成组。
221
+ * sessionId 集合为空时无法识别机器,所有空集合候选共用一个回退组,只取最优 winner。
193
222
  */
194
223
  function selectDailyRepresentatives(bundles) {
195
224
  // 收集每个 (personKey, date) 的所有候选
@@ -212,24 +241,37 @@ function selectDailyRepresentatives(bundles) {
212
241
  const barIdx = key.indexOf("|");
213
242
  const personKey = key.slice(0, barIdx);
214
243
  const date = key.slice(barIdx + 1);
215
- // 贪心分组:候选有 sessionId 且与某组内任意候选的 sessionId 有交集,则并入该组;否则新建组
244
+ // 非空 sessionId 按交集构造连通分量;桥接候选命中多个组时合并这些组,保证传递闭包。
245
+ // 空 sessionId 无法识别机器,统一放入单一回退组,避免重复导出被叠加。
216
246
  const groups = [];
247
+ const emptySessionGroup = [];
217
248
  for (const candidate of candidates) {
218
- let added = false;
219
- if (candidate.sessionIds.size > 0) {
220
- for (const group of groups) {
221
- const hasOverlap = group.some((c) => c.sessionIds.size > 0 && [...candidate.sessionIds].some((s) => c.sessionIds.has(s)));
222
- if (hasOverlap) {
223
- group.push(candidate);
224
- added = true;
225
- break;
226
- }
249
+ if (candidate.sessionIds.size === 0) {
250
+ emptySessionGroup.push(candidate);
251
+ continue;
252
+ }
253
+ const matchingGroupIndexes = [];
254
+ for (let i = 0; i < groups.length; i++) {
255
+ const hasOverlap = groups[i].some((current) => [...candidate.sessionIds].some((sessionId) => current.sessionIds.has(sessionId)));
256
+ if (hasOverlap) {
257
+ matchingGroupIndexes.push(i);
227
258
  }
228
259
  }
229
- if (!added) {
260
+ if (matchingGroupIndexes.length === 0) {
230
261
  groups.push([candidate]);
262
+ continue;
263
+ }
264
+ const targetGroup = groups[matchingGroupIndexes[0]];
265
+ targetGroup.push(candidate);
266
+ for (let i = matchingGroupIndexes.length - 1; i >= 1; i--) {
267
+ const groupIndex = matchingGroupIndexes[i];
268
+ targetGroup.push(...groups[groupIndex]);
269
+ groups.splice(groupIndex, 1);
231
270
  }
232
271
  }
272
+ if (emptySessionGroup.length > 0) {
273
+ groups.push(emptySessionGroup);
274
+ }
233
275
  // 每组取最优代表(同机器多次导出只保留一份)
234
276
  const reps = groups.map((group) => {
235
277
  let best = group[0];
@@ -487,7 +529,7 @@ function recomputeUsage(events) {
487
529
  sevenDayLatestUsagePct: newestFirst.find((event) => event.sevenDayUsagePct !== null)?.sevenDayUsagePct ?? null,
488
530
  };
489
531
  }
490
- /** 展开 detail.csv:同人同天各机器的代表 bundle 事件都列出来,token 总量随本机器当天的 daySummary 附带。 */
532
+ /** 展开 detail.csv:列出代表 bundle 事件,并附带该机器当天的 token 日总量。 */
491
533
  function buildAggregatedDetailRows(bundles) {
492
534
  const repsMap = selectDailyRepresentatives(bundles);
493
535
  const rows = [];
@@ -517,6 +559,41 @@ const ZERO_CODEX = { userMessageCount: 0, apiRequestCount: 0, inputTokens: 0, ou
517
559
  function codexOf(day) {
518
560
  return day.codex ?? ZERO_CODEX;
519
561
  }
562
+ /**
563
+ * 把代表日转换为成本贡献。v10 使用按请求计价后导出的日汇总成本;旧版无法补算,全部请求视为未定价。
564
+ */
565
+ function apiCostContribution(rep) {
566
+ if (rep.bundle.schemaVersion === 10) {
567
+ return {
568
+ result: rep.day.apiEquivalentCost.total,
569
+ catalogVersion: rep.bundle.pricing.catalogVersion,
570
+ legacyRequestCount: 0,
571
+ };
572
+ }
573
+ const requestCount = rep.day.apiRequestCount + codexOf(rep.day).apiRequestCount;
574
+ return {
575
+ result: requestCount === 0
576
+ ? (0, api_equivalent_cost_1.emptyApiEquivalentCost)()
577
+ : { estimatedUsd: null, pricedApiRequestCount: 0, unpricedApiRequestCount: requestCount },
578
+ catalogVersion: null,
579
+ legacyRequestCount: requestCount,
580
+ };
581
+ }
582
+ /**
583
+ * 目录版本只看实际进入代表路径的 bundle。不同 v10 目录或 v10 与有请求的旧版混合时标记为 mixed。
584
+ */
585
+ function mergePricingCatalogVersions(contributions) {
586
+ const versions = new Set(contributions
587
+ .map((contribution) => contribution.catalogVersion)
588
+ .filter((version) => version !== null));
589
+ if (versions.size === 0) {
590
+ return null;
591
+ }
592
+ if (versions.size > 1 || contributions.some((contribution) => contribution.legacyRequestCount > 0)) {
593
+ return "mixed";
594
+ }
595
+ return versions.values().next().value ?? null;
596
+ }
520
597
  /**
521
598
  * 展开 daily.csv:同人同天的不同机器数据直接叠加(计数字段相加),usage 从所有机器该天事件合并后重算。
522
599
  * 同机器重复导出由 selectDailyRepresentatives 在分组阶段去重,不会翻倍。
@@ -527,6 +604,8 @@ function buildAggregatedDailyRows(bundles) {
527
604
  const rows = [];
528
605
  for (const reps of repsMap.values()) {
529
606
  const { personKey, date } = reps[0];
607
+ const costContributions = reps.map(apiCostContribution);
608
+ const apiEquivalentCost = (0, api_equivalent_cost_1.mergeApiEquivalentCosts)(costContributions.map((contribution) => contribution.result));
530
609
  // 不同机器的独立数据直接叠加
531
610
  // 累加量含 Codex:Claude + Codex 同字段相加
532
611
  const userMessageCount = reps.reduce((sum, r) => sum + r.day.userMessageCount + codexOf(r.day).userMessageCount, 0);
@@ -563,6 +642,10 @@ function buildAggregatedDailyRows(bundles) {
563
642
  sevenDayCumulativeUsagePct,
564
643
  uniqueSessions,
565
644
  uniqueWorkspaces,
645
+ estimatedApiEquivalentCostUsd: apiEquivalentCost.estimatedUsd,
646
+ pricedApiRequestCount: apiEquivalentCost.pricedApiRequestCount,
647
+ unpricedApiRequestCount: apiEquivalentCost.unpricedApiRequestCount,
648
+ pricingCatalogVersion: mergePricingCatalogVersions(costContributions),
566
649
  });
567
650
  }
568
651
  return rows.sort((left, right) => `${left.personKey}|${left.date}`.localeCompare(`${right.personKey}|${right.date}`));
@@ -614,6 +697,7 @@ function buildAggregatedWeeklyRows(bundles) {
614
697
  uniqueWorkspaces: 0,
615
698
  days: [],
616
699
  events: [],
700
+ costContributions: [],
617
701
  };
618
702
  groups.set(key, acc);
619
703
  }
@@ -630,6 +714,7 @@ function buildAggregatedWeeklyRows(bundles) {
630
714
  acc.uniqueWorkspaces += day.uniqueWorkspaces;
631
715
  acc.days.push(day);
632
716
  acc.events.push(...(bundleEventsByDate(rep.bundle).get(rep.date) ?? []));
717
+ acc.costContributions.push(apiCostContribution(rep));
633
718
  }
634
719
  }
635
720
  const rows = [];
@@ -640,6 +725,7 @@ function buildAggregatedWeeklyRows(bundles) {
640
725
  const usage = recomputeUsage(claudeEvents);
641
726
  const codexUsage = recomputeUsage(codexEvents);
642
727
  const fallback = fallbackWeeklyUsage(acc.days);
728
+ const apiEquivalentCost = (0, api_equivalent_cost_1.mergeApiEquivalentCosts)(acc.costContributions.map((contribution) => contribution.result));
643
729
  // 整周累计:Claude + Codex 各自在整周子曲线上做分段峰谷和后相加,跨天边界增量被计入,故 weekly ≥ Σ daily。
644
730
  const sevenDayCumulativeUsagePct = computeCumulativeSevenDayBySource(curves, acc.personKey, sliceCurveByWeek, acc.week);
645
731
  rows.push({
@@ -658,6 +744,10 @@ function buildAggregatedWeeklyRows(bundles) {
658
744
  sevenDayCumulativeUsagePct,
659
745
  uniqueSessions: acc.uniqueSessions,
660
746
  uniqueWorkspaces: acc.uniqueWorkspaces,
747
+ estimatedApiEquivalentCostUsd: apiEquivalentCost.estimatedUsd,
748
+ pricedApiRequestCount: apiEquivalentCost.pricedApiRequestCount,
749
+ unpricedApiRequestCount: apiEquivalentCost.unpricedApiRequestCount,
750
+ pricingCatalogVersion: mergePricingCatalogVersions(acc.costContributions),
661
751
  });
662
752
  }
663
753
  return rows.sort((left, right) => `${left.personKey}|${left.week}`.localeCompare(`${right.personKey}|${right.week}`));
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.API_PRICING_METADATA = exports.API_PRICING_CATALOG = void 0;
7
+ exports.validatePricingCatalog = validatePricingCatalog;
8
+ exports.normalizeApiModel = normalizeApiModel;
9
+ exports.findApiModelPrice = findApiModelPrice;
10
+ exports.emptyApiEquivalentCost = emptyApiEquivalentCost;
11
+ exports.priceApiRequest = priceApiRequest;
12
+ exports.mergeApiEquivalentCosts = mergeApiEquivalentCosts;
13
+ const api_pricing_catalog_json_1 = __importDefault(require("./api-pricing-catalog.json"));
14
+ /**
15
+ * 随 ccus 发布的标准同步 API 价格目录。
16
+ * 全部模型价格集中维护在同目录的 api-pricing-catalog.json;价格来源:
17
+ * - https://platform.claude.com/docs/en/about-claude/pricing
18
+ * - https://developers.openai.com/api/docs/models
19
+ */
20
+ exports.API_PRICING_CATALOG = api_pricing_catalog_json_1.default;
21
+ /** 导出元数据直接派生自 JSON,避免版本和计价基准重复维护。 */
22
+ exports.API_PRICING_METADATA = {
23
+ catalogVersion: exports.API_PRICING_CATALOG.catalogVersion,
24
+ currency: exports.API_PRICING_CATALOG.currency,
25
+ basis: exports.API_PRICING_CATALOG.basis,
26
+ };
27
+ function parseTime(value) {
28
+ if (value === null) {
29
+ return Number.POSITIVE_INFINITY;
30
+ }
31
+ return new Date(value).getTime();
32
+ }
33
+ function validateTokenPrices(provider, model, prices) {
34
+ const required = [
35
+ "inputUsdPerMillion",
36
+ "outputUsdPerMillion",
37
+ "cacheReadInputUsdPerMillion",
38
+ ];
39
+ if (provider === "claude") {
40
+ required.push("cacheWrite5mInputUsdPerMillion", "cacheWrite1hInputUsdPerMillion");
41
+ }
42
+ for (const field of required) {
43
+ const value = prices[field];
44
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
45
+ throw new Error(`无效模型价格:${provider}/${model}/${field}`);
46
+ }
47
+ }
48
+ }
49
+ /** 校验生效时间有效,且同来源同模型的半开区间不重叠。 */
50
+ function validatePricingCatalog(catalog) {
51
+ const groups = new Map();
52
+ for (const price of catalog.entries) {
53
+ if ((price.provider !== "claude" && price.provider !== "codex") || price.model.trim() === "") {
54
+ throw new Error("无效价格目录模型");
55
+ }
56
+ validateTokenPrices(price.provider, price.model, price.prices);
57
+ if (price.longContext) {
58
+ if (!Number.isFinite(price.longContext.thresholdInputTokens) || price.longContext.thresholdInputTokens < 0) {
59
+ throw new Error(`无效长上下文阈值:${price.provider}/${price.model}`);
60
+ }
61
+ validateTokenPrices(price.provider, price.model, price.longContext.prices);
62
+ }
63
+ const start = parseTime(price.effectiveFrom);
64
+ const end = parseTime(price.effectiveTo);
65
+ if (!Number.isFinite(start) || Number.isNaN(end) || end <= start) {
66
+ throw new Error(`无效价格生效区间:${price.provider}/${price.model}`);
67
+ }
68
+ const key = `${price.provider}\0${price.model}`;
69
+ const items = groups.get(key) ?? [];
70
+ items.push(price);
71
+ groups.set(key, items);
72
+ }
73
+ for (const [key, items] of groups) {
74
+ items.sort((left, right) => parseTime(left.effectiveFrom) - parseTime(right.effectiveFrom));
75
+ for (let index = 1; index < items.length; index += 1) {
76
+ if (parseTime(items[index].effectiveFrom) < parseTime(items[index - 1].effectiveTo)) {
77
+ throw new Error(`价格生效区间重叠:${key.replace("\0", "/")}`);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ function normalizeClaudeModel(model) {
83
+ const value = model.trim().toLowerCase()
84
+ .replace(/^anthropic\//, "")
85
+ .replaceAll("_", "-")
86
+ .replace(/\[1m\]$/, "")
87
+ .replace(/-thinking(?=-\d{8}$|$)/, "");
88
+ const canonicalMinor = /^claude-(opus|sonnet|haiku|fable)-(\d+)\.(\d+)(?:-\d{8})?$/.exec(value);
89
+ if (canonicalMinor) {
90
+ return `claude-${canonicalMinor[1]}-${canonicalMinor[2]}.${canonicalMinor[3]}`;
91
+ }
92
+ const modern = /^claude-(opus|sonnet|haiku|fable)-(\d+)-(\d+)(?:-\d{8})?$/.exec(value);
93
+ if (modern) {
94
+ return `claude-${modern[1]}-${modern[2]}.${modern[3]}`;
95
+ }
96
+ const modernMajor = /^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-\d{8})?$/.exec(value);
97
+ if (modernMajor) {
98
+ return `claude-${modernMajor[1]}-${modernMajor[2]}`;
99
+ }
100
+ const legacyMinor = /^claude-(\d+)-(\d+)-(opus|sonnet|haiku|fable)(?:-\d{8})?$/.exec(value);
101
+ if (legacyMinor) {
102
+ return `claude-${legacyMinor[3]}-${legacyMinor[1]}.${legacyMinor[2]}`;
103
+ }
104
+ const legacyMajor = /^claude-(\d+)-(opus|sonnet|haiku|fable)(?:-\d{8})?$/.exec(value);
105
+ if (legacyMajor) {
106
+ return `claude-${legacyMajor[2]}-${legacyMajor[1]}`;
107
+ }
108
+ return null;
109
+ }
110
+ function normalizeCodexModel(model) {
111
+ let value = model.trim().toLowerCase().replace(/^openai\//, "").replaceAll("_", "-");
112
+ value = value.replace(/[\s/(]+(?:reasoning\s*:?\s*)?(?:minimal|low|medium|high|xhigh|max|ultra)\)?$/, "");
113
+ value = value.replace(/-(?:minimal|low|medium|high|xhigh|max|ultra)$/, "");
114
+ value = value.replace(/-\d{4}-\d{2}-\d{2}$/, "");
115
+ if (value === "gpt-5.6") {
116
+ return "gpt-5.6-sol";
117
+ }
118
+ return /^gpt-[a-z0-9.-]+$/.test(value) ? value : null;
119
+ }
120
+ function normalizeApiModel(provider, model) {
121
+ if (!model) {
122
+ return null;
123
+ }
124
+ return provider === "claude" ? normalizeClaudeModel(model) : normalizeCodexModel(model);
125
+ }
126
+ function findApiModelPrice(request, catalog = exports.API_PRICING_CATALOG) {
127
+ const model = normalizeApiModel(request.provider, request.model);
128
+ const timestamp = new Date(request.timestamp).getTime();
129
+ if (!model || !Number.isFinite(timestamp)) {
130
+ return null;
131
+ }
132
+ return catalog.entries.find((price) => price.provider === request.provider
133
+ && price.model === model
134
+ && timestamp >= parseTime(price.effectiveFrom)
135
+ && timestamp < parseTime(price.effectiveTo)) ?? null;
136
+ }
137
+ function tokenCount(value) {
138
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
139
+ }
140
+ function computeRequestUsd(request, price) {
141
+ const inputTokens = tokenCount(request.inputTokens);
142
+ const cacheReadInputTokens = tokenCount(request.cacheReadInputTokens);
143
+ const cacheWrite5mInputTokens = tokenCount(request.cacheWrite5mInputTokens);
144
+ const cacheWrite1hInputTokens = tokenCount(request.cacheWrite1hInputTokens);
145
+ const totalInputTokens = inputTokens + cacheReadInputTokens + cacheWrite5mInputTokens + cacheWrite1hInputTokens;
146
+ const prices = price.longContext && totalInputTokens > price.longContext.thresholdInputTokens
147
+ ? price.longContext.prices
148
+ : price.prices;
149
+ return (inputTokens * prices.inputUsdPerMillion
150
+ + tokenCount(request.outputTokens) * prices.outputUsdPerMillion
151
+ + cacheReadInputTokens * prices.cacheReadInputUsdPerMillion
152
+ + cacheWrite5mInputTokens * (prices.cacheWrite5mInputUsdPerMillion ?? 0)
153
+ + cacheWrite1hInputTokens * (prices.cacheWrite1hInputUsdPerMillion ?? 0)) / 1_000_000;
154
+ }
155
+ function emptyApiEquivalentCost() {
156
+ return { estimatedUsd: 0, pricedApiRequestCount: 0, unpricedApiRequestCount: 0 };
157
+ }
158
+ /** 对单次请求计价;无法匹配价格时保留为一条未定价请求。 */
159
+ function priceApiRequest(request, catalog = exports.API_PRICING_CATALOG) {
160
+ const price = findApiModelPrice(request, catalog);
161
+ if (!price) {
162
+ return { estimatedUsd: null, pricedApiRequestCount: 0, unpricedApiRequestCount: 1 };
163
+ }
164
+ return {
165
+ estimatedUsd: computeRequestUsd(request, price),
166
+ pricedApiRequestCount: 1,
167
+ unpricedApiRequestCount: 0,
168
+ };
169
+ }
170
+ /** 合并来源、日期或请求结果,并保持空范围/部分覆盖/全未定价语义。 */
171
+ function mergeApiEquivalentCosts(results) {
172
+ let estimatedUsd = 0;
173
+ let pricedApiRequestCount = 0;
174
+ let unpricedApiRequestCount = 0;
175
+ for (const result of results) {
176
+ pricedApiRequestCount += result.pricedApiRequestCount;
177
+ unpricedApiRequestCount += result.unpricedApiRequestCount;
178
+ if (result.estimatedUsd !== null) {
179
+ estimatedUsd += result.estimatedUsd;
180
+ }
181
+ }
182
+ return {
183
+ estimatedUsd: pricedApiRequestCount > 0 || unpricedApiRequestCount === 0 ? estimatedUsd : null,
184
+ pricedApiRequestCount,
185
+ unpricedApiRequestCount,
186
+ };
187
+ }
188
+ validatePricingCatalog(exports.API_PRICING_CATALOG);
189
+ //# sourceMappingURL=api-equivalent-cost.js.map