dsh-codearts 0.5.2 → 1.0.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.
@@ -830,12 +830,11 @@ var CodeArtsAccountStore = class {
830
830
  //#endregion
831
831
  //#region src/catalog.ts
832
832
  /**
833
- * Static seed roster. Ids are the *upstream registered* spellings (case
834
- * matters — `canonicalModel` in upstream.ts passes these through verbatim).
835
- * Updated 2026-09 from codearts2api's known model set.
833
+ * 参数表(不是模型名单):只给**发现到的**模型补 `contextWindow` / `maxTokens` /
834
+ * `supportsImages`,绝不会因为这里有条目就把模型塞进目录。
836
835
  *
837
836
  * maxTokens 注记(2026-09-30 实测,scripts/param-probe.mjs 直连上游二分验证):
838
- * 免费福利 DeepSeek 模型的 max_tokens 服务端硬上限是 **65536**,更大的值
837
+ * 福利 DeepSeek 模型的 max_tokens 服务端硬上限是 **65536**,更大的值
839
838
  * (73728 / 98304 / 131072 / 384000 / 393216)一律流内报
840
839
  * InferHub.001001005.400 "The request param is invalid, Please check it"。
841
840
  * 此前的 384000/393216 是占位值:DSH 会把 catalog 的 maxTokens 作为
@@ -900,36 +899,73 @@ const FALLBACK_CODEARTS_MODELS = [
900
899
  },
901
900
  {
902
901
  id: "deepseek-v4-flash-0731",
903
- name: "DeepSeek-V4-Flash (免费)",
902
+ name: "DeepSeek-V4-Flash (福利)",
904
903
  contextWindow: 131072,
905
904
  maxTokens: 65536,
906
905
  supportsImages: true
907
906
  },
908
907
  {
909
908
  id: "deepseek-v4-pro-0813",
910
- name: "DeepSeek-V4-Pro (免费)",
909
+ name: "DeepSeek-V4-Pro (福利)",
911
910
  contextWindow: 131072,
912
911
  maxTokens: 65536,
913
912
  supportsImages: true
914
913
  },
915
914
  {
916
915
  id: "deepseek-v4.1-flash",
917
- name: "DeepSeek-V4.1-Flash (免费)",
916
+ name: "DeepSeek-V4.1-Flash",
918
917
  contextWindow: 131072,
919
918
  maxTokens: 65536,
920
919
  supportsImages: true
921
920
  },
922
921
  {
923
922
  id: "glm-5.3-flash",
924
- name: "GLM-5.3-Flash (免费)",
923
+ name: "GLM-5.3-Flash (福利)",
925
924
  contextWindow: 128e3,
926
925
  maxTokens: 8e3,
927
926
  supportsImages: true
928
927
  }
929
928
  ];
929
+ /**
930
+ * 倍率的展示写法:福利恒为 `x0.00`,其余用上游的数值,拿不到就不显示。
931
+ *
932
+ * 福利模型不走积分,倍率显示成 0 比显示上游某个残留 credit 更接近事实。
933
+ */
934
+ function creditRatioLabel(ratio, benefit) {
935
+ if (benefit) return "x0.00";
936
+ const value = (ratio ?? "").trim().replace(/x$/i, "").trim();
937
+ return /^\d+(?:\.\d+)?$/.test(value) ? `x${Number(value).toFixed(2)}` : void 0;
938
+ }
939
+ /**
940
+ * 模型在宿主下拉里的**名字**带一个标记尾巴:` · x0.18`、` · x0.00 · 福利`。
941
+ *
942
+ * 写法照搬 dsh-codebuddy-cli 的 provider:倍率在前、上游徽章措辞在后,统一用
943
+ * ` · ` 连接(`Name · x0.71 · 限时免费`)。宿主自己的模型下拉只渲染文字,
944
+ * 插不进 Tag,所以那是用户唯一能看见标记的地方;插件卡片会反过来把尾巴剥掉、
945
+ * 改挂 Tag(见 client 的 modelName),两边都不会重复。
946
+ */
947
+ function modelDisplayName(name, benefit, ratio) {
948
+ const base = stripModelSuffixes(name);
949
+ const parts = [creditRatioLabel(ratio, benefit), benefit ? "福利" : void 0].filter((part) => part !== void 0);
950
+ return parts.length === 0 ? base : `${base} · ${parts.join(" · ")}`;
951
+ }
952
+ /**
953
+ * 剥掉模型名尾巴上的标记:` · x0.18`、` · x0.00 · 福利`、`(福利)`、`(免费)`、
954
+ * `(x0.18)` 等(兼容旧格式)。
955
+ */
956
+ function stripModelSuffixes(name) {
957
+ let base = name;
958
+ for (;;) {
959
+ const next = base.replace(/\s*·\s*(?:x\d+(?:\.\d+)?|(?:限时)?(?:免费|福利)(?:\s+x\d+(?:\.\d+)?)?)\s*$/, "");
960
+ if (next === base) break;
961
+ base = next;
962
+ }
963
+ return base.replace(/\s*[((]\s*(?:(?:免费|福利)(?:\s+x\d+(?:\.\d+)?)?|x\d+(?:\.\d+)?)\s*[))]\s*$/, "").trim();
964
+ }
930
965
  /** Mutable catalog shared by the shim's `/v1/models` and the adapter. */
931
966
  var CodeArtsCatalog = class {
932
- models = FALLBACK_CODEARTS_MODELS;
967
+ /** 空目录起步:没发现过就是没有模型,不拿历史名单冒充。 */
968
+ models = [];
933
969
  current() {
934
970
  return this.models;
935
971
  }
@@ -947,19 +983,15 @@ function filterEnabledModels(models, enabled) {
947
983
  const kept = models.filter((model) => allow.has(model.id));
948
984
  return kept.length === 0 ? models : kept;
949
985
  }
950
- const benefitLower = new Set([
951
- "deepseek-v4-flash-0731",
952
- "deepseek-v4-pro-0813",
953
- "deepseek-v4.1-flash",
954
- "glm-5.3-flash"
955
- ].map((m) => m.toLowerCase()));
956
986
  /**
957
- * 动态发现确认的福利模型(小写 id)。
987
+ * 动态发现确认的福利模型(小写 id)—— 这是「是不是福利」的**唯一**依据。
988
+ *
989
+ * 只认 `gateway/config` 给过的名单,没有静态兜底:猜错一次的代价不对称 ——
990
+ * 漏带 `maas_type: benefit` 是福利模型报 404(它本来就不可用),多带一次却是把
991
+ * 一个正常能用的积分模型推进一条不存在的渠道,直接 benefit not found。
958
992
  *
959
993
  * 与 codearts2api 的差别:那边按账号隔离目录,本插件的聊天链路(shim → upstream)
960
- * 拿不到账号上下文,所以这里取**并集**并保留种子兜底 —— 宁可多带一次
961
- * `maas_type: benefit` 头,也不要漏带导致 404。套餐轮换后模型「转正」时,
962
- * 下次发现会把它从这份集合里去掉。
994
+ * 拿不到账号上下文,所以这里取所有成功账号的并集。
963
995
  */
964
996
  const discoveredBenefit = /* @__PURE__ */ new Set();
965
997
  /** 用一次发现的结果替换动态福利集合(传空数组表示「本轮确认没有福利模型」)。 */
@@ -973,15 +1005,45 @@ function setDiscoveredBenefitModels(ids) {
973
1005
  /**
974
1006
  * 该模型是否走限时福利路由(按小写 ID 判定)。
975
1007
  *
976
- * 发现结果优先于种子:目录里明确「不是福利」的模型(已转正)不会被种子重新标成福利,
977
- * 因为发现成功时会把它从 `discoveredBenefit` 里剔除,而种子只在该集合为空时兜底。
1008
+ * 只认发现结果:`gateway/config` 没给过的模型一律不算福利,没有静态兜底。
978
1009
  */
979
1010
  function isBenefitModel(id) {
1011
+ return discoveredBenefit.has(id.trim().toLowerCase());
1012
+ }
1013
+ /**
1014
+ * 今天被上游判定「没有福利权益」的模型(小写 id -> 本地日期键)。
1015
+ *
1016
+ * 判据只有一个:聊天回 `InferHub.4004.200 benefit not found`。上游没有任何接口能
1017
+ * 提前告诉我们福利余量(`statistics/plugin` 的 `model_quota_num` 实测恒为 -1,
1018
+ * 唯一能给数的 opengw balance 也不覆盖福利池),所以只能「失败一次才知道」。
1019
+ *
1020
+ * 按本地日期存:福利是每天发的,昨天的失败不代表今天。卡片据此把模型标成
1021
+ * 「福利今日不可用」,但**不**替用户取消勾选 —— 那是改他的设置。
1022
+ */
1023
+ const benefitBlocked = /* @__PURE__ */ new Map();
1024
+ function localDayKey(at = Date.now()) {
1025
+ const d = new Date(at);
1026
+ const month = String(d.getMonth() + 1).padStart(2, "0");
1027
+ const day = String(d.getDate()).padStart(2, "0");
1028
+ return `${d.getFullYear()}-${month}-${day}`;
1029
+ }
1030
+ /** 记一次「这个模型今天没有福利权益」。 */
1031
+ function markBenefitBlocked(id) {
980
1032
  const key = id.trim().toLowerCase();
981
- if (key === "") return false;
982
- if (discoveredBenefit.has(key)) return true;
983
- if (discoveredBenefit.size > 0) return false;
984
- return benefitLower.has(key);
1033
+ if (key !== "") benefitBlocked.set(key, localDayKey());
1034
+ }
1035
+ /** 该模型今天是否已经因为福利权益缺失失败过。 */
1036
+ function isBenefitBlocked(id) {
1037
+ const key = id.trim().toLowerCase();
1038
+ const day = benefitBlocked.get(key);
1039
+ if (day === void 0) return false;
1040
+ if (day === localDayKey()) return true;
1041
+ benefitBlocked.delete(key);
1042
+ return false;
1043
+ }
1044
+ /** 清空福利缺失记录 —— 手动刷新模型列表时调,给用户一个自救出口。 */
1045
+ function clearBenefitBlocked() {
1046
+ benefitBlocked.clear();
985
1047
  }
986
1048
  /**
987
1049
  * 合并多路来源的模型清单(内置 / 限时福利 / agent-center)。
@@ -1016,12 +1078,14 @@ function richerModel(current, next) {
1016
1078
  const benefit = current.benefit === true || next.benefit === true;
1017
1079
  const keep = richerThan(next, current) ? next : current;
1018
1080
  const drop = keep === next ? current : next;
1081
+ const ratio = keep.ratio ?? drop.ratio;
1019
1082
  return {
1020
1083
  id: keep.id,
1021
1084
  name: keep.name || drop.name || keep.id,
1022
1085
  contextWindow: keep.contextWindow > 0 ? keep.contextWindow : drop.contextWindow,
1023
1086
  maxTokens: keep.maxTokens > 0 ? keep.maxTokens : drop.maxTokens,
1024
1087
  supportsImages: keep.supportsImages || drop.supportsImages,
1088
+ ...ratio === void 0 ? {} : { ratio },
1025
1089
  ...benefit ? { benefit: true } : {}
1026
1090
  };
1027
1091
  }
@@ -1032,29 +1096,26 @@ function richerThan(a, b) {
1032
1096
  return a.id !== a.id.toLowerCase() && b.id === b.id.toLowerCase();
1033
1097
  }
1034
1098
  /**
1035
- * 用种子清单给**发现结果**补缺:种子只补,不覆盖。
1099
+ * 用参数表给**发现结果**补 0 值字段。参数表里有、上游这次没返回的模型 —— 不塞进目录。
1036
1100
  *
1037
- * 为什么不能把种子一起丢进 `mergeModelLists`:那个函数按「参数更全/大小写更像注册名」
1038
- * 选记录,种子里的占位值(例如 OpenPangu 的 ctx 32000 / max 4000)会凭大小写规则
1101
+ * 为什么不能把参数表一起丢进 `mergeModelLists`:那个函数按「参数更全/大小写更像注册名」
1102
+ * 选记录,表里的占位值(例如 OpenPangu 的 ctx 32000 / max 4000)会凭大小写规则
1039
1103
  * 压过上游返回的真实值(ctx 524288 / max 131072)。所以顺序必须是
1040
- * 「先合并发现结果 → 再用种子补 0 值字段与缺失条目」。
1104
+ * 「先合并发现结果 → 再用参数表补 0 值字段」。
1041
1105
  */
1042
- function fillFromSeed(discovered, seed = FALLBACK_CODEARTS_MODELS) {
1106
+ function backfillParams(discovered, params = FALLBACK_CODEARTS_MODELS) {
1043
1107
  const byId = /* @__PURE__ */ new Map();
1044
1108
  for (const model of discovered) byId.set(model.id.toLowerCase(), { ...model });
1045
- for (const fallback of seed) {
1046
- const key = fallback.id.toLowerCase();
1047
- const current = byId.get(key);
1048
- if (current === void 0) {
1049
- byId.set(key, { ...fallback });
1050
- continue;
1051
- }
1052
- byId.set(key, {
1109
+ for (const fallback of params) {
1110
+ const current = byId.get(fallback.id.toLowerCase());
1111
+ if (current === void 0) continue;
1112
+ byId.set(fallback.id.toLowerCase(), {
1053
1113
  id: current.id,
1054
1114
  name: current.name || fallback.name,
1055
1115
  contextWindow: current.contextWindow > 0 ? current.contextWindow : fallback.contextWindow,
1056
1116
  maxTokens: current.maxTokens > 0 ? current.maxTokens : fallback.maxTokens,
1057
1117
  supportsImages: current.supportsImages || fallback.supportsImages,
1118
+ ...current.ratio === void 0 ? {} : { ratio: current.ratio },
1058
1119
  ...current.benefit === true ? { benefit: true } : {}
1059
1120
  });
1060
1121
  }
@@ -1161,10 +1222,12 @@ function signRequest(req, body, cred) {
1161
1222
  * @module dsh-codearts/upstream
1162
1223
  */
1163
1224
  var upstream_exports = /* @__PURE__ */ __exportAll({
1225
+ BENEFIT_MISSING_CODE: () => BENEFIT_MISSING_CODE,
1164
1226
  CONCURRENCY_CAP_CODE: () => CONCURRENCY_CAP_CODE,
1165
1227
  MAX_TOKENS_CEILING: () => MAX_TOKENS_CEILING,
1166
1228
  PARAM_INVALID_CODE: () => PARAM_INVALID_CODE,
1167
1229
  SNAP_HOST: () => SNAP_HOST,
1230
+ TPM_LIMIT_CODE: () => TPM_LIMIT_CODE,
1168
1231
  canonicalModel: () => canonicalModel,
1169
1232
  chatStream: () => chatStream,
1170
1233
  claimBenefit: () => claimBenefit,
@@ -1174,13 +1237,18 @@ var upstream_exports = /* @__PURE__ */ __exportAll({
1174
1237
  fetchCallerIdentity: () => fetchCallerIdentity,
1175
1238
  fetchModels: () => fetchModels,
1176
1239
  fetchWelfareCampaigns: () => fetchWelfareCampaigns,
1240
+ isBenefitMissing: () => isBenefitMissing,
1177
1241
  isConcurrencyCap: () => isConcurrencyCap,
1178
1242
  isParamInvalidFrame: () => isParamInvalidFrame,
1243
+ isQueueableRate: () => isQueueableRate,
1244
+ isTpmLimit: () => isTpmLimit,
1179
1245
  isWelfareClaimedStatus: () => isWelfareClaimedStatus,
1180
1246
  maxTokensCeilingFor: () => maxTokensCeilingFor,
1181
1247
  pickDailyWelfareCampaign: () => pickDailyWelfareCampaign,
1182
1248
  prepareChatBody: () => prepareChatBody,
1183
- signedGet: () => signedGet
1249
+ rateLimitFrame: () => rateLimitFrame,
1250
+ signedGet: () => signedGet,
1251
+ upstreamErrorMessage: () => upstreamErrorMessage
1184
1252
  });
1185
1253
  const SNAP_HOST = "https://snap-access.cn-north-4.myhuaweicloud.com";
1186
1254
  const EP_CHAT = "/api/v2/chat/completions";
@@ -1448,6 +1516,85 @@ const CONCURRENCY_CAP_CODE = "TM.00001041";
1448
1516
  function isConcurrencyCap(result) {
1449
1517
  return !result.ok && result.message.includes("TM.00001041");
1450
1518
  }
1519
+ /**
1520
+ * 模型级 TPM(tokens per minute)配额超限。2026-10-21 实测:它和并发上限不同,
1521
+ * 是**流内首帧**(HTTP 200)回的,所以照样绕过了 classify;换号也没用(配额按
1522
+ * 模型共享),能做的只有本地排队等窗口滑过去。
1523
+ *
1524
+ * 上游的码并不固定:同一小时内见过 `InferHub.ModelArts.81111.429`("The model TPM
1525
+ * limit has been significantly exceeded.")和 `InferHub.ModelArts.81114.429`
1526
+ * ("Too many requests, the rate limit is 50000000 tokens per minute.")。所以按
1527
+ * 形状认(ModelArts.*.429)而不是按某一个常量认。
1528
+ */
1529
+ const TPM_LIMIT_CODE = "InferHub.ModelArts.81111.429";
1530
+ const MODEL_RATE_CODE = /InferHub\.ModelArts\.\d+\.429/;
1531
+ const MODEL_RATE_TEXT = /tpm limit|rate limit|too many requests/i;
1532
+ function isTpmLimit(result) {
1533
+ if (result.ok) return false;
1534
+ return MODEL_RATE_CODE.test(result.message) || MODEL_RATE_TEXT.test(result.message);
1535
+ }
1536
+ /** 撞到这两类限流都值得原地等,而不是直接甩给 pi-ai 重试。 */
1537
+ function isQueueableRate(result) {
1538
+ return isConcurrencyCap(result) || isTpmLimit(result);
1539
+ }
1540
+ /**
1541
+ * 从一帧已转成 OpenAI 形状的 SSE 里认出「可排队限流」。
1542
+ *
1543
+ * 只在**还没有任何内容产出**的首帧上使用:此时重发是安全的;一旦下游已经收到
1544
+ * 字节,HTTP 头也发出去了,就只能把错误帧原样交出去。
1545
+ */
1546
+ function rateLimitFrame(text) {
1547
+ if (!text.includes("error")) return void 0;
1548
+ for (const line of text.split("\n")) {
1549
+ const trimmed = line.trim();
1550
+ if (!trimmed.startsWith("data:")) continue;
1551
+ const payload = trimmed.slice(5).trim();
1552
+ if (payload === "" || payload === "[DONE]") continue;
1553
+ let ev;
1554
+ try {
1555
+ ev = JSON.parse(payload);
1556
+ } catch {
1557
+ continue;
1558
+ }
1559
+ const code = String(ev.error?.code ?? ev.error_code ?? "");
1560
+ const message = String(ev.error?.message ?? ev.error_msg ?? "");
1561
+ const probe = {
1562
+ ok: false,
1563
+ kind: "soft_rate",
1564
+ status: 429,
1565
+ message: `${code}: ${message}`
1566
+ };
1567
+ if (code === "TM.00001041" || isTpmLimit(probe)) return {
1568
+ code: code === "" ? TPM_LIMIT_CODE : code,
1569
+ message
1570
+ };
1571
+ }
1572
+ }
1573
+ /**
1574
+ * 把上游错误翻译成用户能看懂的一句话。
1575
+ *
1576
+ * 这些错误都发生在「账号、插件都正常」的时候 —— 原样把英文码甩给用户,只会
1577
+ * 让人以为是自己配错了。默认分支仍保留原始信息,便于排查。
1578
+ */
1579
+ function upstreamErrorMessage(message, status = 0) {
1580
+ if (MODEL_RATE_CODE.test(message) || MODEL_RATE_TEXT.test(message)) return "华为侧该模型当前请求过多(共享 TPM 超限),不是你的账号或插件问题。稍后重试,或临时改用 GLM-5.2 / GLM-5.3。";
1581
+ if (message.includes("TM.00001041")) return "CodeArts 免费档同时只允许 3 个会话,当前槽位一直占满(被打断的会话约 1 分钟才释放)。请稍后重试。";
1582
+ if (isBenefitMissing("", message)) return "这个模型属于限时福利,当前福利通道不可用(不是账号问题)。可以刷新模型列表,改用非福利模型。";
1583
+ if (message.toLowerCase().includes("model is not registered")) return "上游没有注册这个模型,它可能已经下线;请刷新模型列表后重选。";
1584
+ return `codearts 上游错误 (http ${status}): ${message.slice(0, 400)}`;
1585
+ }
1586
+ /**
1587
+ * 「这个账号当前没有该模型的限时福利权益」。2026-10-21 实测:上游回 HTTP 200,
1588
+ * 错误在**流内帧**里(不在状态码上),所以它既不走 retry 也不走换号,用户看到的
1589
+ * 就是正文里裸裸一句 `benefit not found`。
1590
+ *
1591
+ * 这条不是请求写错了,重发也不会好:福利模型只有福利路由,去掉 `maas_type: benefit`
1592
+ * 就变成 InferHub.002002009.404 model is not registered。唯一出路是等活动回来。
1593
+ */
1594
+ const BENEFIT_MISSING_CODE = "InferHub.4004.200";
1595
+ function isBenefitMissing(code, message) {
1596
+ return code === "InferHub.4004.200" || message.toLowerCase().includes("benefit not found");
1597
+ }
1451
1598
  function classify(status, message) {
1452
1599
  const m = message.toLowerCase();
1453
1600
  if (status === 401 || status === 403) return "session_dead";
@@ -1524,6 +1671,7 @@ async function chatStream(credential, prepared, signal) {
1524
1671
  }
1525
1672
  if (!resp.ok) {
1526
1673
  const text = await resp.text().catch(() => "");
1674
+ if (isBenefitMissing("", text)) markBenefitBlocked(prepared.model);
1527
1675
  return {
1528
1676
  ok: false,
1529
1677
  kind: classify(resp.status, text),
@@ -1582,6 +1730,7 @@ async function chatStream(credential, prepared, signal) {
1582
1730
  continue;
1583
1731
  }
1584
1732
  if (ev.error_code && ev.error_code !== "0" && ev.error_code !== 0) {
1733
+ if (isBenefitMissing(String(ev.error_code), String(ev.error_msg ?? ""))) markBenefitBlocked(model);
1585
1734
  controller.enqueue(openAIData({
1586
1735
  choices: [{
1587
1736
  delta: { content: "" },
@@ -1727,6 +1876,21 @@ function asArray(value) {
1727
1876
  return Array.isArray(value) ? value : [];
1728
1877
  }
1729
1878
  /**
1879
+ * 从 `credit[]` 里取常规时段的展示倍率(原样 `0.18x`)。
1880
+ *
1881
+ * 上游给每个时段一条(`default` / `off_peak`,实测 2026-10-21)。这里只取
1882
+ * `default`:尖峰价才是用户平时付的,低峰 0.09x 属于额外优惠,拿来当标题会低估。
1883
+ * 没有 credit 的模型(如 Qwen3-VL)返回 undefined —— 界面上就不显示倍率。
1884
+ */
1885
+ function creditRatioOf(record) {
1886
+ const credits = asArray(record.credit).map(asRecord);
1887
+ if (credits.length === 0) return void 0;
1888
+ const pick = credits.find((entry) => entry.time_slot_id === "default") ?? credits.find((entry) => entry.time_slot_id === null || entry.time_slot_id === void 0) ?? credits[0];
1889
+ if (pick === void 0) return void 0;
1890
+ const match = /^\s*(\d+(?:\.\d+)?)\s*x\s*$/i.exec(firstString(pick.ratio_display));
1891
+ return match === null ? void 0 : `${match[1]}x`;
1892
+ }
1893
+ /**
1730
1894
  * 内置模型:`GET /v1/model/builtin`(`Agent-Type: PromptCenter`)。
1731
1895
  * 实测响应 `{ count, builtinModels: [{ model_id, model_name, model_desc,
1732
1896
  * enable, display_enabled, supports_images, ... }] }` —— 注意它**不带**
@@ -1742,12 +1906,14 @@ async function fetchBuiltinModels(credential, signal) {
1742
1906
  if (model.enable === false) continue;
1743
1907
  const id = firstString(model.model_id, model.model_name);
1744
1908
  if (id === "") continue;
1909
+ const ratio = creditRatioOf(model);
1745
1910
  out.push({
1746
1911
  id,
1747
1912
  name: firstString(model.model_name, id),
1748
1913
  contextWindow: positive(model.context_window),
1749
1914
  maxTokens: positive(model.max_tokens),
1750
- supportsImages: model.supports_images === true
1915
+ supportsImages: model.supports_images === true,
1916
+ ...ratio === void 0 ? {} : { ratio }
1751
1917
  });
1752
1918
  }
1753
1919
  return out;
@@ -1768,12 +1934,14 @@ async function fetchBenefitModels(credential, signal) {
1768
1934
  const model = asRecord(raw);
1769
1935
  const id = firstString(model.model_id, model.model_name);
1770
1936
  if (id === "") continue;
1937
+ const ratio = creditRatioOf(model);
1771
1938
  out.push({
1772
1939
  id,
1773
1940
  name: firstString(model.model_name, id),
1774
1941
  contextWindow: positive(model.context_window),
1775
1942
  maxTokens: positive(model.max_tokens),
1776
1943
  supportsImages: model.supports_images === true,
1944
+ ...ratio === void 0 ? {} : { ratio },
1777
1945
  benefit: true
1778
1946
  });
1779
1947
  }
@@ -1813,12 +1981,14 @@ async function fetchAgentModels(credential, signal) {
1813
1981
  const params = asRecord(model.model_parameters);
1814
1982
  const id = firstString(params.model_id, model.model_id, model.model_alias, model.model_name);
1815
1983
  if (id === "") continue;
1984
+ const ratio = creditRatioOf(model) ?? creditRatioOf(params);
1816
1985
  out.push({
1817
1986
  id,
1818
1987
  name: firstString(model.model_name, model.model_alias, id),
1819
1988
  contextWindow: positive(params.context_window),
1820
1989
  maxTokens: positive(params.max_tokens),
1821
- supportsImages: model.supports_images === true
1990
+ supportsImages: model.supports_images === true,
1991
+ ...ratio === void 0 ? {} : { ratio }
1822
1992
  });
1823
1993
  }
1824
1994
  return out;
@@ -1874,7 +2044,7 @@ async function fetchModels(credential, signal) {
1874
2044
  discovered.push(item.models);
1875
2045
  }
1876
2046
  if (sources.length === 0) throw new Error(`model discovery failed on every source: ${errors.map((e) => `${e.source}: ${e.message}`).join("; ")}`);
1877
- const merged = fillFromSeed(mergeModelLists(discovered));
2047
+ const merged = backfillParams(mergeModelLists(discovered));
1878
2048
  if (merged.length === 0) throw new Error("model discovery returned an empty catalog");
1879
2049
  return {
1880
2050
  models: merged.map((model) => ({
@@ -2181,8 +2351,14 @@ async function discoverModels(store, signal) {
2181
2351
  async function refreshCatalog(store, catalog, signal) {
2182
2352
  const outcome = await discoverModels(store, signal);
2183
2353
  if (!outcome.anySucceeded || outcome.models.length === 0) return outcome;
2184
- catalog.set(outcome.models);
2185
2354
  setDiscoveredBenefitModels(outcome.models.filter((model) => model.benefit === true).map((model) => model.id));
2355
+ catalog.set(outcome.models.map((model) => {
2356
+ const benefit = isBenefitModel(model.id);
2357
+ return {
2358
+ ...model,
2359
+ name: modelDisplayName(model.name, benefit, model.ratio)
2360
+ };
2361
+ }));
2186
2362
  return outcome;
2187
2363
  }
2188
2364
  //#endregion
@@ -2352,8 +2528,13 @@ var CodeArtsUsageTracker = class {
2352
2528
  snapshot(at = this.now()) {
2353
2529
  const todayKey = localDateKey(at);
2354
2530
  const today = this.days.get(todayKey);
2531
+ const cutoff = localDateKey(at - 2592e6);
2355
2532
  const allTime = emptyTotals();
2356
- for (const day of this.days.values()) for (const totals of Object.values(day.models)) mergeTotals(allTime, totals);
2533
+ const last30 = emptyTotals();
2534
+ for (const [date, day] of this.days) for (const totals of Object.values(day.models)) {
2535
+ mergeTotals(allTime, totals);
2536
+ if (date >= cutoff) mergeTotals(last30, totals);
2537
+ }
2357
2538
  const models = today?.models ?? {};
2358
2539
  return {
2359
2540
  today: {
@@ -2361,6 +2542,7 @@ var CodeArtsUsageTracker = class {
2361
2542
  ...today === void 0 ? {} : sumOf(models)
2362
2543
  },
2363
2544
  allTime,
2545
+ last30,
2364
2546
  byModelToday: Object.entries(models).map(([id, totals]) => ({
2365
2547
  id,
2366
2548
  totals: { ...totals }
@@ -2404,4 +2586,4 @@ function sumOf(store) {
2404
2586
  return out;
2405
2587
  }
2406
2588
  //#endregion
2407
- export { isBenefitModel as C, codeartsAuthPath as D, codeartsAccountsPath as E, startLogin as O, filterEnabledModels as S, CodeArtsCredentialStore as T, pickDailyWelfareCampaign as _, SNAP_HOST as a, CodeArtsCatalog as b, claimDailyWelfare as c, fetchModels as d, fetchWelfareCampaigns as f, maxTokensCeilingFor as g, isWelfareClaimedStatus as h, refreshCatalog as i, degradeTokenCeiling as l, isParamInvalidFrame as m, codeartsUsagePath as n, canonicalModel as o, isConcurrencyCap as p, discoverModels as r, chatStream as s, CodeArtsUsageTracker as t, fetchCallerIdentity as u, prepareChatBody as v, CodeArtsAccountStore as w, FALLBACK_CODEARTS_MODELS as x, signedGet as y };
2589
+ export { isBenefitModel as A, upstreamErrorMessage as C, creditRatioLabel as D, clearBenefitBlocked as E, startLogin as F, CodeArtsCredentialStore as M, codeartsAccountsPath as N, filterEnabledModels as O, codeartsAuthPath as P, signedGet as S, FALLBACK_CODEARTS_MODELS as T, isWelfareClaimedStatus as _, SNAP_HOST as a, prepareChatBody as b, claimDailyWelfare as c, fetchModels as d, fetchWelfareCampaigns as f, isTpmLimit as g, isQueueableRate as h, refreshCatalog as i, CodeArtsAccountStore as j, isBenefitBlocked as k, degradeTokenCeiling as l, isParamInvalidFrame as m, codeartsUsagePath as n, canonicalModel as o, isConcurrencyCap as p, discoverModels as r, chatStream as s, CodeArtsUsageTracker as t, fetchCallerIdentity as u, maxTokensCeilingFor as v, CodeArtsCatalog as w, rateLimitFrame as x, pickDailyWelfareCampaign as y };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dsh-codearts",
3
3
  "displayName": "DSH CodeArts Connect",
4
4
  "description": "把华为云 CodeArts 接入 DeepSeek Harness —— 内置 OAuth2 登录(PKCE + DPoP),登录后即可在 DSH 对话窗口使用 CodeArts 的模型。",
5
- "version": "0.5.2",
5
+ "version": "1.0.0",
6
6
  "packageManager": "pnpm@11.24.0",
7
7
  "author": "elegant01",
8
8
  "keywords": [