@kenz1117/dsh-ui-usage-billing 1.0.1 → 1.0.3

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/lib/index.js CHANGED
@@ -70,7 +70,21 @@ const PLAN_KNOWLEDGE = {
70
70
  "minimax": { type: "code" },
71
71
  "minimax-cn": { type: "code" },
72
72
  "minimax-token-plan": { type: "code" },
73
- "minimax-token-plan-cn": { type: "code" }
73
+ "minimax-token-plan-cn": { type: "code" },
74
+ "hunyuan-token-plan": { type: "code" },
75
+ "tencent-token-plan": { type: "code" },
76
+ "hy-token-plan": { type: "code" },
77
+ "xinghuo-token-plan": { type: "code" },
78
+ "xfyun-coding": { type: "code" },
79
+ "spark-coding": { type: "code" },
80
+ "huawei-token-plan": { type: "code" },
81
+ "pangu-token-plan": { type: "code" },
82
+ "huawei-maas-token-plan": { type: "code" },
83
+ "volcengine-agent-plan": { type: "code" },
84
+ "ark-agent-plan": { type: "code" },
85
+ "baidu-token-plan": { type: "code" },
86
+ "ernie-token-plan": { type: "code" },
87
+ "wenxin-token-plan": { type: "code" }
74
88
  };
75
89
  /**
76
90
  * 订阅/plan provider id 变体 → PLAN_KNOWLEDGE 规范键(引用 dsh-spend 的别名归一化)。
@@ -312,6 +326,23 @@ const MODEL_CATALOG = [
312
326
  output: 28
313
327
  }
314
328
  },
329
+ {
330
+ key: "glm-5.3-flash",
331
+ name: "GLM-5.3-Flash",
332
+ provider: "智谱 AI",
333
+ colorVar: "dsw-static-blue-300",
334
+ price: {
335
+ currency: "CNY",
336
+ input: .8,
337
+ cacheHit: .23,
338
+ output: 2.8
339
+ },
340
+ promo: {
341
+ factor: .5,
342
+ endsAtMs: Date.UTC(2026, 8, 8, 16, 0, 0),
343
+ note: "限时 5 折"
344
+ }
345
+ },
315
346
  {
316
347
  key: "glm-4.6",
317
348
  name: "GLM-4.6",
@@ -392,10 +423,66 @@ const MODEL_CATALOG = [
392
423
  colorVar: "dsw-static-blue-600",
393
424
  price: {
394
425
  currency: "CNY",
395
- input: 13.58,
396
- cacheHit: 1.36,
397
- output: 40.74
398
- }
426
+ input: 12,
427
+ cacheHit: 1.5,
428
+ output: 36
429
+ },
430
+ extraRows: [
431
+ {
432
+ label: "显式缓存创建",
433
+ input: 15
434
+ },
435
+ {
436
+ label: "显式缓存命中",
437
+ input: 1
438
+ },
439
+ {
440
+ label: "Batch File",
441
+ input: 6,
442
+ output: 18,
443
+ note: "长期半价"
444
+ },
445
+ {
446
+ label: "Batch Chat",
447
+ input: 12,
448
+ output: 36,
449
+ note: "与标准价一致"
450
+ }
451
+ ]
452
+ },
453
+ {
454
+ key: "qwen-3.8-flash",
455
+ name: "Qwen3.8 Flash",
456
+ provider: "阿里通义",
457
+ colorVar: "dsw-static-blue-400",
458
+ price: {
459
+ currency: "CNY",
460
+ input: 1,
461
+ cacheHit: .1,
462
+ output: 3
463
+ },
464
+ extraRows: [
465
+ {
466
+ label: "显式缓存创建",
467
+ input: 1.25
468
+ },
469
+ {
470
+ label: "显式缓存命中",
471
+ input: .1
472
+ },
473
+ {
474
+ label: "Batch File",
475
+ input: .5,
476
+ output: 1.5,
477
+ note: "长期半价"
478
+ },
479
+ {
480
+ label: "Batch Chat",
481
+ input: 1,
482
+ output: 3,
483
+ note: "与标准价一致"
484
+ }
485
+ ]
399
486
  },
400
487
  {
401
488
  key: "qwen-max",
@@ -404,10 +491,36 @@ const MODEL_CATALOG = [
404
491
  colorVar: "dsw-static-blue-300",
405
492
  price: {
406
493
  currency: "CNY",
407
- input: 6,
408
- cacheHit: .6,
409
- output: 18
410
- }
494
+ input: 12,
495
+ cacheHit: 1.2,
496
+ output: 36
497
+ },
498
+ promo: {
499
+ factor: .5,
500
+ note: "限时 5 折"
501
+ },
502
+ extraRows: [
503
+ {
504
+ label: "显式缓存创建",
505
+ input: 15
506
+ },
507
+ {
508
+ label: "显式缓存命中",
509
+ input: 1.2
510
+ },
511
+ {
512
+ label: "Batch File",
513
+ input: 6,
514
+ output: 18,
515
+ note: "长期半价"
516
+ },
517
+ {
518
+ label: "Batch Chat",
519
+ input: 12,
520
+ output: 36,
521
+ note: "与标准价一致"
522
+ }
523
+ ]
411
524
  },
412
525
  {
413
526
  key: "qwen-plus",
@@ -419,7 +532,29 @@ const MODEL_CATALOG = [
419
532
  input: .8,
420
533
  cacheHit: .08,
421
534
  output: 4.8
422
- }
535
+ },
536
+ extraRows: [
537
+ {
538
+ label: "显式缓存创建",
539
+ input: 1
540
+ },
541
+ {
542
+ label: "显式缓存命中",
543
+ input: .08
544
+ },
545
+ {
546
+ label: "Batch File",
547
+ input: .4,
548
+ output: 2.4,
549
+ note: "长期半价"
550
+ },
551
+ {
552
+ label: "Batch Chat",
553
+ input: .8,
554
+ output: 4.8,
555
+ note: "与标准价一致"
556
+ }
557
+ ]
423
558
  },
424
559
  {
425
560
  key: "qwen-flash",
@@ -431,7 +566,14 @@ const MODEL_CATALOG = [
431
566
  input: .2,
432
567
  cacheHit: .02,
433
568
  output: 2
434
- }
569
+ },
570
+ extraRows: [{
571
+ label: "显式缓存创建",
572
+ input: .25
573
+ }, {
574
+ label: "显式缓存命中",
575
+ input: .02
576
+ }]
435
577
  },
436
578
  {
437
579
  key: "doubao",
@@ -469,6 +611,42 @@ const MODEL_CATALOG = [
469
611
  output: 8
470
612
  }
471
613
  },
614
+ {
615
+ key: "doubao-seed-evolving",
616
+ name: "Doubao-Seed-Evolving",
617
+ provider: "字节豆包",
618
+ colorVar: "dsw-static-red-500",
619
+ price: {
620
+ currency: "CNY",
621
+ input: 6,
622
+ cacheHit: 1.2,
623
+ output: 30
624
+ }
625
+ },
626
+ {
627
+ key: "doubao-seed-2.1-pro",
628
+ name: "Doubao Seed-2.1 Pro",
629
+ provider: "字节豆包",
630
+ colorVar: "dsw-static-red-400",
631
+ price: {
632
+ currency: "CNY",
633
+ input: 6,
634
+ cacheHit: 1.2,
635
+ output: 30
636
+ }
637
+ },
638
+ {
639
+ key: "doubao-seed-2.1-turbo",
640
+ name: "Doubao Seed-2.1 Turbo",
641
+ provider: "字节豆包",
642
+ colorVar: "dsw-static-red-300",
643
+ price: {
644
+ currency: "CNY",
645
+ input: 3,
646
+ cacheHit: .6,
647
+ output: 15
648
+ }
649
+ },
472
650
  {
473
651
  key: "kimi",
474
652
  name: "Kimi K2.7 Code",
@@ -795,6 +973,343 @@ const MODEL_CATALOG = [
795
973
  output: .3
796
974
  }
797
975
  },
976
+ {
977
+ key: "claude-opus-4-6",
978
+ name: "Claude Opus 4.6",
979
+ provider: "Anthropic",
980
+ colorVar: "dsw-static-red-500",
981
+ price: {
982
+ currency: "USD",
983
+ input: 5,
984
+ cacheHit: .5,
985
+ output: 25
986
+ }
987
+ },
988
+ {
989
+ key: "claude-sonnet-4-6",
990
+ name: "Claude Sonnet 4.6",
991
+ provider: "Anthropic",
992
+ colorVar: "dsw-static-red-400",
993
+ price: {
994
+ currency: "USD",
995
+ input: 3,
996
+ cacheHit: .3,
997
+ output: 15
998
+ }
999
+ },
1000
+ {
1001
+ key: "claude-haiku-4-5",
1002
+ name: "Claude Haiku 4.5",
1003
+ provider: "Anthropic",
1004
+ colorVar: "dsw-static-red-300",
1005
+ price: {
1006
+ currency: "USD",
1007
+ input: 1,
1008
+ cacheHit: .1,
1009
+ output: 5
1010
+ }
1011
+ },
1012
+ {
1013
+ key: "claude-opus-5",
1014
+ name: "Claude Opus 5",
1015
+ provider: "Anthropic",
1016
+ colorVar: "dsw-static-red-500",
1017
+ price: {
1018
+ currency: "USD",
1019
+ input: 5,
1020
+ cacheHit: .5,
1021
+ output: 25
1022
+ }
1023
+ },
1024
+ {
1025
+ key: "claude-sonnet-5",
1026
+ name: "Claude Sonnet 5",
1027
+ provider: "Anthropic",
1028
+ colorVar: "dsw-static-red-400",
1029
+ price: {
1030
+ currency: "USD",
1031
+ input: 2,
1032
+ cacheHit: .2,
1033
+ output: 10
1034
+ }
1035
+ },
1036
+ {
1037
+ key: "mistral-large-2512",
1038
+ name: "Mistral Large 3",
1039
+ provider: "Mistral AI",
1040
+ colorVar: "dsw-static-violet-500",
1041
+ price: {
1042
+ currency: "USD",
1043
+ input: .5,
1044
+ cacheHit: .05,
1045
+ output: 1.5
1046
+ }
1047
+ },
1048
+ {
1049
+ key: "mistral-small-2603",
1050
+ name: "Mistral Small 4",
1051
+ provider: "Mistral AI",
1052
+ colorVar: "dsw-static-violet-400",
1053
+ price: {
1054
+ currency: "USD",
1055
+ input: .15,
1056
+ cacheHit: .015,
1057
+ output: .6
1058
+ }
1059
+ },
1060
+ {
1061
+ key: "ministral-8b-latest",
1062
+ name: "Ministral 8B",
1063
+ provider: "Mistral AI",
1064
+ colorVar: "dsw-static-violet-300",
1065
+ price: {
1066
+ currency: "USD",
1067
+ input: .1,
1068
+ cacheHit: .01,
1069
+ output: .1
1070
+ }
1071
+ },
1072
+ {
1073
+ key: "command-a-03-2025",
1074
+ name: "Command A",
1075
+ provider: "Cohere",
1076
+ colorVar: "dsw-static-cyan-500",
1077
+ price: {
1078
+ currency: "USD",
1079
+ input: 2.5,
1080
+ cacheHit: .25,
1081
+ output: 10
1082
+ }
1083
+ },
1084
+ {
1085
+ key: "command-r-08-2024",
1086
+ name: "Command R",
1087
+ provider: "Cohere",
1088
+ colorVar: "dsw-static-cyan-400",
1089
+ price: {
1090
+ currency: "USD",
1091
+ input: .15,
1092
+ cacheHit: .015,
1093
+ output: .6
1094
+ }
1095
+ },
1096
+ {
1097
+ key: "longcat-2.0",
1098
+ name: "LongCat 2.0",
1099
+ provider: "美团",
1100
+ colorVar: "dsw-static-amber-500",
1101
+ price: {
1102
+ currency: "CNY",
1103
+ input: 4,
1104
+ cacheHit: .8,
1105
+ output: 16
1106
+ },
1107
+ estimated: true
1108
+ },
1109
+ {
1110
+ key: "minicpm-v-4.5",
1111
+ name: "MiniCPM-V 4.5",
1112
+ provider: "面壁智能",
1113
+ colorVar: "dsw-static-green-500",
1114
+ price: {
1115
+ currency: "CNY",
1116
+ input: 1,
1117
+ cacheHit: .2,
1118
+ output: 4
1119
+ },
1120
+ estimated: true
1121
+ },
1122
+ {
1123
+ key: "ernie-4.5",
1124
+ name: "ERNIE-4.5 300B",
1125
+ provider: "百度文心",
1126
+ colorVar: "dsw-static-blue-300",
1127
+ price: {
1128
+ currency: "CNY",
1129
+ input: 2,
1130
+ cacheHit: .4,
1131
+ output: 8
1132
+ },
1133
+ estimated: true
1134
+ },
1135
+ {
1136
+ key: "dots-3-note-preview",
1137
+ name: "Dots3-Note Preview",
1138
+ provider: "小红书",
1139
+ colorVar: "dsw-static-red-500",
1140
+ price: {
1141
+ currency: "CNY",
1142
+ input: 2,
1143
+ cacheHit: .4,
1144
+ output: 8
1145
+ },
1146
+ estimated: true
1147
+ },
1148
+ {
1149
+ key: "qwen3.6-max",
1150
+ name: "Qwen3.6 Max",
1151
+ provider: "阿里通义",
1152
+ colorVar: "dsw-static-orange-500",
1153
+ price: {
1154
+ currency: "CNY",
1155
+ input: 9,
1156
+ cacheHit: .9,
1157
+ output: 54
1158
+ },
1159
+ extraRows: [{
1160
+ label: "显式缓存创建",
1161
+ input: 11.25
1162
+ }, {
1163
+ label: "显式缓存命中",
1164
+ input: .9
1165
+ }]
1166
+ },
1167
+ {
1168
+ key: "qwen3-coder-plus",
1169
+ name: "Qwen3-Coder Plus",
1170
+ provider: "阿里通义",
1171
+ colorVar: "dsw-static-orange-400",
1172
+ price: {
1173
+ currency: "CNY",
1174
+ input: 4,
1175
+ cacheHit: .8,
1176
+ output: 16
1177
+ },
1178
+ extraRows: [{
1179
+ label: "显式缓存创建",
1180
+ input: 5
1181
+ }, {
1182
+ label: "显式缓存命中",
1183
+ input: .4
1184
+ }]
1185
+ },
1186
+ {
1187
+ key: "qwen3-coder",
1188
+ name: "Qwen3-Coder 480B",
1189
+ provider: "阿里通义",
1190
+ colorVar: "dsw-static-orange-300",
1191
+ price: {
1192
+ currency: "CNY",
1193
+ input: 4,
1194
+ cacheHit: .8,
1195
+ output: 16
1196
+ },
1197
+ estimated: true
1198
+ },
1199
+ {
1200
+ key: "glm-4.5-x",
1201
+ name: "GLM-4.5-X",
1202
+ provider: "智谱 AI",
1203
+ colorVar: "dsw-static-blue-400",
1204
+ price: {
1205
+ currency: "CNY",
1206
+ input: 4,
1207
+ cacheHit: 1,
1208
+ output: 16
1209
+ },
1210
+ estimated: true
1211
+ },
1212
+ {
1213
+ key: "glm-5.2-fast",
1214
+ name: "GLM-5.2 Fast",
1215
+ provider: "智谱 AI",
1216
+ colorVar: "dsw-static-blue-300",
1217
+ price: {
1218
+ currency: "CNY",
1219
+ input: 6,
1220
+ cacheHit: 1.2,
1221
+ output: 24
1222
+ },
1223
+ estimated: true
1224
+ },
1225
+ {
1226
+ key: "kimi-k3-fast",
1227
+ name: "Kimi K3 Fast",
1228
+ provider: "月之暗面",
1229
+ colorVar: "dsw-static-cyan-400",
1230
+ price: {
1231
+ currency: "CNY",
1232
+ input: 20,
1233
+ cacheHit: 2,
1234
+ output: 100
1235
+ },
1236
+ estimated: true
1237
+ },
1238
+ {
1239
+ key: "kimi-k2.7-code-fast",
1240
+ name: "Kimi K2.7 Code Fast",
1241
+ provider: "月之暗面",
1242
+ colorVar: "dsw-static-cyan-400",
1243
+ price: {
1244
+ currency: "CNY",
1245
+ input: 6.5,
1246
+ cacheHit: 1.3,
1247
+ output: 27
1248
+ },
1249
+ estimated: true
1250
+ },
1251
+ {
1252
+ key: "kimi-k2.6-fast",
1253
+ name: "Kimi K2.6 Fast",
1254
+ provider: "月之暗面",
1255
+ colorVar: "dsw-static-cyan-300",
1256
+ price: {
1257
+ currency: "CNY",
1258
+ input: 6.5,
1259
+ cacheHit: 1.1,
1260
+ output: 27
1261
+ },
1262
+ estimated: true
1263
+ },
1264
+ {
1265
+ key: "kimi-k2.6-turbo",
1266
+ name: "Kimi K2.6 Turbo",
1267
+ provider: "月之暗面",
1268
+ colorVar: "dsw-static-cyan-300",
1269
+ price: {
1270
+ currency: "CNY",
1271
+ input: 6.5,
1272
+ cacheHit: 1.1,
1273
+ output: 27
1274
+ },
1275
+ estimated: true
1276
+ },
1277
+ {
1278
+ key: "kimi-k2-thinking-turbo",
1279
+ name: "Kimi K2 Thinking Turbo",
1280
+ provider: "月之暗面",
1281
+ colorVar: "dsw-static-cyan-300",
1282
+ price: {
1283
+ currency: "CNY",
1284
+ input: 8,
1285
+ cacheHit: 1,
1286
+ output: 58
1287
+ }
1288
+ },
1289
+ {
1290
+ key: "doubao-seed-2.0-code",
1291
+ name: "Doubao Seed-2.0 Code",
1292
+ provider: "字节豆包",
1293
+ colorVar: "dsw-static-red-500",
1294
+ price: {
1295
+ currency: "CNY",
1296
+ input: 3.2,
1297
+ cacheHit: .64,
1298
+ output: 16
1299
+ }
1300
+ },
1301
+ {
1302
+ key: "doubao-seed-2.0-lite",
1303
+ name: "Doubao Seed-2.0 Lite",
1304
+ provider: "字节豆包",
1305
+ colorVar: "dsw-static-red-300",
1306
+ price: {
1307
+ currency: "CNY",
1308
+ input: .6,
1309
+ cacheHit: .12,
1310
+ output: 3.6
1311
+ }
1312
+ },
798
1313
  {
799
1314
  key: "other",
800
1315
  name: "其他模型",
@@ -802,10 +1317,10 @@ const MODEL_CATALOG = [
802
1317
  colorVar: "dsw-static-neutral-bluish-500",
803
1318
  price: {
804
1319
  currency: "CNY",
805
- input: .5,
806
- cacheHit: .25,
807
- cacheMiss: .5,
808
- output: 1.5
1320
+ input: 0,
1321
+ cacheHit: 0,
1322
+ cacheMiss: 0,
1323
+ output: 0
809
1324
  }
810
1325
  }
811
1326
  ];
@@ -825,10 +1340,66 @@ const MODEL_KEY_ALIASES = {
825
1340
  "glm-4.7": "glm-4.7",
826
1341
  "glm-5-turbo": "glm-5-turbo",
827
1342
  "glm-5.1": "glm-5.1",
1343
+ "glm-5.3-flash": "glm-5.3-flash",
828
1344
  "glm-5v-turbo": "glm-5v-turbo",
829
1345
  "glm-5v.1": "glm-5v-turbo",
1346
+ "claude-opus-4-6": "claude-opus-4-6",
1347
+ "claude-opus-4.6": "claude-opus-4-6",
1348
+ "claude-sonnet-4-6": "claude-sonnet-4-6",
1349
+ "claude-sonnet-4.6": "claude-sonnet-4-6",
1350
+ "claude-haiku-4-5": "claude-haiku-4-5",
1351
+ "claude-haiku-4.5": "claude-haiku-4-5",
1352
+ "claude-opus-5": "claude-opus-5",
1353
+ "claude-sonnet-5": "claude-sonnet-5",
1354
+ "mistral-large-2512": "mistral-large-2512",
1355
+ "mistral-large-3": "mistral-large-2512",
1356
+ "mistral-small-2603": "mistral-small-2603",
1357
+ "mistral-small-4": "mistral-small-2603",
1358
+ "ministral-8b-latest": "ministral-8b-latest",
1359
+ "ministral-8b": "ministral-8b-latest",
1360
+ "command-a-03-2025": "command-a-03-2025",
1361
+ "command-a": "command-a-03-2025",
1362
+ "command-r-08-2024": "command-r-08-2024",
1363
+ "command-r": "command-r-08-2024",
1364
+ "longcat-2.0": "longcat-2.0",
1365
+ "longcat-2": "longcat-2.0",
1366
+ "minicpm-v-4.5": "minicpm-v-4.5",
1367
+ "minicpm-v-4.5-thinking": "minicpm-v-4.5",
1368
+ "ernie-4.5": "ernie-4.5",
1369
+ "ernie-4.5-300b": "ernie-4.5",
1370
+ "dots-3-note-preview": "dots-3-note-preview",
1371
+ "dots-3-note": "dots-3-note-preview",
1372
+ "dots3-note-preview": "dots-3-note-preview",
1373
+ "rednote-dots3": "dots-3-note-preview",
1374
+ "doubao-seed-evolving": "doubao-seed-evolving",
1375
+ "doubao-seed-evolve": "doubao-seed-evolving",
1376
+ "doubao-seed-2.1-pro": "doubao-seed-2.1-pro",
1377
+ "doubao-seed-2.1-pro-290000": "doubao-seed-2.1-pro",
1378
+ "doubao-seed-2.1-turbo": "doubao-seed-2.1-turbo",
1379
+ "doubao-seed-2-1-turbo": "doubao-seed-2.1-turbo",
830
1380
  "qwen3.8-max": "qwen-3.8-max",
1381
+ "qwen3.8-flash": "qwen-3.8-flash",
831
1382
  "qwen3.7-max": "qwen-max",
1383
+ "qwen3.6-max": "qwen3.6-max",
1384
+ "qwen3.6-max-preview": "qwen3.6-max",
1385
+ "qwen3-coder-plus": "qwen3-coder-plus",
1386
+ "qwen3-coder": "qwen3-coder",
1387
+ "qwen3-coder-480b": "qwen3-coder",
1388
+ "glm-4.5-x": "glm-4.5-x",
1389
+ "glm-4.5x": "glm-4.5-x",
1390
+ "glm-5.2-fast": "glm-5.2-fast",
1391
+ "glm-5.2f": "glm-5.2-fast",
1392
+ "kimi-k3-fast": "kimi-k3-fast",
1393
+ "kimi-k3f": "kimi-k3-fast",
1394
+ "kimi-k2.7-code-fast": "kimi-k2.7-code-fast",
1395
+ "kimi-k2.7-code-f": "kimi-k2.7-code-fast",
1396
+ "kimi-k2.6-fast": "kimi-k2.6-fast",
1397
+ "kimi-k2.6-turbo": "kimi-k2.6-turbo",
1398
+ "kimi-k2-thinking-turbo": "kimi-k2-thinking-turbo",
1399
+ "doubao-seed-2.0-code": "doubao-seed-2.0-code",
1400
+ "doubao-seed-2-0-code": "doubao-seed-2.0-code",
1401
+ "doubao-seed-2.0-lite": "doubao-seed-2.0-lite",
1402
+ "doubao-seed-2-0-lite": "doubao-seed-2.0-lite",
832
1403
  "qwen-max": "qwen-max",
833
1404
  "hunyuan-t1": "hunyuan-t1",
834
1405
  "step-3.7-flash": "step",
@@ -952,6 +1523,42 @@ function isPriced(key) {
952
1523
  return FALLBACK_RATES.some((rate) => rate.key.toLowerCase() === resolved.toLowerCase());
953
1524
  }
954
1525
  /**
1526
+ * 促销在 nowMs 是否生效:factor 必须落在 (0,1) 区间,截止时刻及之后视为过期;
1527
+ * endsAtMs 缺省表示长期活动,在 factor 合法期间持续生效。
1528
+ * 导出供测试:纯函数。
1529
+ * @param promo - 待判定的促销窗口。
1530
+ * @param nowMs - 判定时刻(epoch ms)。
1531
+ */
1532
+ function isPromoActive(promo, nowMs) {
1533
+ const expired = promo.endsAtMs !== void 0 && nowMs >= promo.endsAtMs;
1534
+ return Number.isFinite(nowMs) && !expired && promo.factor > 0 && promo.factor < 1;
1535
+ }
1536
+ /**
1537
+ * 把限时促销折入条目单价:生效期内返回 price 主档与 offPeak 全部乘 factor 的
1538
+ * 副本,其余字段原样保留;不在促销期(过期/未开始/factor 非法)原样返回。
1539
+ * 幂等由调用方保证——计价与费率表显示各自只折一次,勿对已折价副本重复应用。
1540
+ * @param entry - 目录条目(price 保持刊例价口径)。
1541
+ * @param nowMs - 判定时刻(epoch ms)。
1542
+ */
1543
+ function applyPromo(entry, nowMs) {
1544
+ const { promo } = entry;
1545
+ if (promo === void 0 || !isPromoActive(promo, nowMs)) return entry;
1546
+ const scaled = (band) => ({
1547
+ input: band.input * promo.factor,
1548
+ cacheHit: band.cacheHit * promo.factor,
1549
+ ...band.cacheMiss !== void 0 ? { cacheMiss: band.cacheMiss * promo.factor } : {},
1550
+ output: band.output * promo.factor
1551
+ });
1552
+ return {
1553
+ ...entry,
1554
+ price: {
1555
+ ...scaled(entry.price),
1556
+ currency: entry.price.currency,
1557
+ ...entry.price.offPeak !== void 0 ? { offPeak: scaled(entry.price.offPeak) } : {}
1558
+ }
1559
+ };
1560
+ }
1561
+ /**
955
1562
  * Price one band's token usage in CNY. The stats `input` field is the TOTAL
956
1563
  * prompt tokens (cacheHit + cacheMiss), so billing splits it: the cache-hit
957
1564
  * share prices at the hit rate and the remaining share at the miss rate.
@@ -980,15 +1587,17 @@ function priceBandCost(band, buckets, currency) {
980
1587
  * @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
981
1588
  * @returns the estimated cost in CNY.
982
1589
  */
983
- function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE) {
984
- const peak = priceBandCost(entry.price, buckets, entry.price.currency);
985
- const off = entry.price.offPeak === void 0 ? peak : priceBandCost(entry.price.offPeak, buckets, entry.price.currency);
1590
+ function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE, nowMs = Date.now()) {
1591
+ const priced = applyPromo(entry, nowMs);
1592
+ const peak = priceBandCost(priced.price, buckets, priced.price.currency);
1593
+ const off = priced.price.offPeak === void 0 ? peak : priceBandCost(priced.price.offPeak, buckets, priced.price.currency);
986
1594
  return peak * peakShare + off * (1 - peakShare);
987
1595
  }
988
1596
  /**
989
1597
  * 按调用时刻精确判定高峰/空闲档并计价(P0-1:替代固定比例混合)。时刻未知
990
1598
  * (null/NaN,理论不发生在真实事件流)时回退 {@link DEFAULT_PEAK_SHARE} 混合,
991
- * 保持旧语义不低估。平档模型(无 offPeak)两个时段同价。
1599
+ * 保持旧语义不低估。平档模型(无 offPeak)两个时段同价。限时促销与峰谷档
1600
+ * 同口径:按事件时刻判定该笔流量当时享受的单价。
992
1601
  * @param entry - the catalog entry whose prices apply.
993
1602
  * @param buckets - token usage counts.
994
1603
  * @param timeMs - the call's wall-clock time (epoch ms); null falls back to the peak-share mix.
@@ -996,9 +1605,10 @@ function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE) {
996
1605
  * @returns the estimated cost in CNY(USD 计价模型已按当前汇率折算)。
997
1606
  */
998
1607
  function computeCostAt(entry, buckets, timeMs, peakShare = DEFAULT_PEAK_SHARE) {
999
- if (entry.price.offPeak === void 0) return priceBandCost(entry.price, buckets, entry.price.currency);
1000
1608
  if (timeMs === null || timeMs === void 0 || !Number.isFinite(timeMs)) return computeCost(entry, buckets, peakShare);
1001
- return priceBandCost(tierAt(timeMs) === "peak" ? entry.price : entry.price.offPeak, buckets, entry.price.currency);
1609
+ const priced = applyPromo(entry, timeMs);
1610
+ if (priced.price.offPeak === void 0) return priceBandCost(priced.price, buckets, priced.price.currency);
1611
+ return priceBandCost(tierAt(timeMs) === "peak" ? priced.price : priced.price.offPeak, buckets, priced.price.currency);
1002
1612
  }
1003
1613
  /**
1004
1614
  * Format an amount with adaptive precision and the given currency symbol.
@@ -3980,6 +4590,7 @@ const UsageBillingSettingsSchema = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z
3980
4590
  const PRICING_REFRESH_INTERVAL_MS = 360 * 60 * 1e3;
3981
4591
  /** 订阅套餐额度缓存时长(毫秒):上游配额 API 低频变化,5 分钟足够。 */
3982
4592
  const SUBSCRIPTION_CACHE_MS = 300 * 1e3;
4593
+ const BALANCE_CACHE_MS = 300 * 1e3;
3983
4594
  /** DeepSeek 余额查询的默认凭据引用(与 llm-deepseek 的默认引用一致)。 */
3984
4595
  const DEFAULT_BALANCE_API_KEY_ENV = "DEEPSEEK_API_KEY";
3985
4596
  /**
@@ -4463,6 +5074,10 @@ function apply(ctx, config = {}) {
4463
5074
  res.end(JSON.stringify(live));
4464
5075
  }
4465
5076
  }), "usage-billing: pricing route");
5077
+ let balanceCache = {
5078
+ at: 0,
5079
+ doc: { balances: [] }
5080
+ };
4466
5081
  ctx.effect(() => ctx.webServer.register({
4467
5082
  kind: "exact",
4468
5083
  path: "/api/billing/balance",
@@ -4476,39 +5091,53 @@ function apply(ctx, config = {}) {
4476
5091
  ...providers["deepseek"],
4477
5092
  apiKeyEnv: config.balanceApiKeyEnv ?? DEFAULT_BALANCE_API_KEY_ENV
4478
5093
  };
4479
- const balances = await queryBalances(ctx, providers);
4480
- const custom = await queryCustomBalances(ctx, config.customBalances ?? []);
4481
- const declared = await queryDeclaredEndpoints(ctx, piProviders, config.declaredEndpoints ?? []);
4482
- for (const row of [
4483
- ...balances,
4484
- ...custom,
4485
- ...declared
4486
- ]) if (row.error === "unauthorized") warnAuthOnce("balance", row.provider, row.displayName);
4487
- const official = balances.find((row) => row.provider === "deepseek");
4488
- let reconcile;
4489
- if (official !== void 0 && official.totalBalance !== void 0) {
4490
- const now = Date.now();
4491
- const today = dayStamp(now);
4492
- let todayOfficialCost = 0;
4493
- try {
4494
- todayOfficialCost = (await aggregator.aggregate()).byDay[today]?.officialCost ?? 0;
4495
- } catch {}
4496
- const result = reconcileBalanceDelta(reconcileRef, official, todayOfficialCost, today, now);
4497
- reconcileRef = result.ref;
4498
- if (result.event !== null && result.event.kind !== "flat") reconcile = {
4499
- ...result.event,
4500
- provider: official.displayName
4501
- };
4502
- if (result.ref !== null) persistReconcileRef();
5094
+ if (Date.now() - balanceCache.at < BALANCE_CACHE_MS) {
5095
+ res.end(JSON.stringify(balanceCache.doc));
5096
+ return;
4503
5097
  }
4504
- res.end(JSON.stringify({
4505
- balances: [
5098
+ try {
5099
+ const balances = await queryBalances(ctx, providers);
5100
+ const custom = await queryCustomBalances(ctx, config.customBalances ?? []);
5101
+ const declared = await queryDeclaredEndpoints(ctx, piProviders, config.declaredEndpoints ?? []);
5102
+ for (const row of [
4506
5103
  ...balances,
4507
5104
  ...custom,
4508
5105
  ...declared
4509
- ],
4510
- ...reconcile === void 0 ? {} : { reconcile }
4511
- }));
5106
+ ]) if (row.error === "unauthorized") warnAuthOnce("balance", row.provider, row.displayName);
5107
+ const official = balances.find((row) => row.provider === "deepseek");
5108
+ let reconcile;
5109
+ if (official !== void 0 && official.totalBalance !== void 0) {
5110
+ const now = Date.now();
5111
+ const today = dayStamp(now);
5112
+ let todayOfficialCost = 0;
5113
+ try {
5114
+ todayOfficialCost = (await aggregator.aggregate()).byDay[today]?.officialCost ?? 0;
5115
+ } catch {}
5116
+ const result = reconcileBalanceDelta(reconcileRef, official, todayOfficialCost, today, now);
5117
+ reconcileRef = result.ref;
5118
+ if (result.event !== null && result.event.kind !== "flat") reconcile = {
5119
+ ...result.event,
5120
+ provider: official.displayName
5121
+ };
5122
+ if (result.ref !== null) persistReconcileRef();
5123
+ }
5124
+ const doc = {
5125
+ balances: [
5126
+ ...balances,
5127
+ ...custom,
5128
+ ...declared
5129
+ ],
5130
+ ...reconcile === void 0 ? {} : { reconcile }
5131
+ };
5132
+ balanceCache = {
5133
+ at: Date.now(),
5134
+ doc
5135
+ };
5136
+ res.end(JSON.stringify(doc));
5137
+ } catch (error) {
5138
+ console.warn("[usage-billing] refreshBalances failed; serving cached balances:", error);
5139
+ res.end(JSON.stringify(balanceCache.doc));
5140
+ }
4512
5141
  }
4513
5142
  }), "usage-billing: balance route");
4514
5143
  ctx.effect(() => ctx.webServer.register({