dsh-codearts 1.2.3 → 1.2.5

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.d.mts CHANGED
@@ -1296,12 +1296,40 @@ interface UsageTotals {
1296
1296
  promptTokens: number;
1297
1297
  completionTokens: number;
1298
1298
  totalTokens: number;
1299
+ /**
1300
+ * 命中上游 prompt 缓存的输入 token(OpenAI 兼容口径)。
1301
+ *
1302
+ * 上游若在 usage 帧里带 `prompt_tokens_details.cached_tokens`(也有平铺成
1303
+ * `cached_tokens` / `cache_tokens` / `prompt_cache_hit_tokens` 的),这里才可能
1304
+ * 是正数;不给就恒为 0。UI 据此决定显不显示「缓存」那一段 —— 不臆造。
1305
+ * 它是 `promptTokens` 的**子集**,不是额外的输入量。
1306
+ */
1307
+ cachedTokens: number;
1308
+ /**
1309
+ * 其中属于「思考」的输出 token(OpenAI 兼容口径:`completion_tokens_details.reasoning_tokens`,
1310
+ * 平铺成顶层 `reasoning_tokens` 的也认)。
1311
+ *
1312
+ * 推理模型(deepseek-v4.1-flash 这类)会把它单列出来,通常照样计费;不单独记就
1313
+ * 只能看见一个偏大的「输出」。它是 `completionTokens` 的**子集**,不是额外的输出量。
1314
+ * 上游不给就恒为 0。
1315
+ */
1316
+ reasoningTokens: number;
1299
1317
  }
1300
1318
  /** 上游 usage 帧里我们关心的字段。 */
1301
1319
  interface RawUsage {
1302
1320
  prompt_tokens?: unknown;
1303
1321
  completion_tokens?: unknown;
1304
1322
  total_tokens?: unknown;
1323
+ /** OpenAI 兼容的嵌套写法:`prompt_tokens_details: { cached_tokens }`。 */
1324
+ prompt_tokens_details?: unknown;
1325
+ /** 平铺写法,几家兼容层各用各的名字,都认。 */
1326
+ cached_tokens?: unknown;
1327
+ cache_tokens?: unknown;
1328
+ prompt_cache_hit_tokens?: unknown;
1329
+ /** 推理模型的嵌套写法:`completion_tokens_details: { reasoning_tokens }`。 */
1330
+ completion_tokens_details?: unknown;
1331
+ /** 平铺的思考 token 别名。 */
1332
+ reasoning_tokens?: unknown;
1305
1333
  }
1306
1334
  interface UsageSnapshot {
1307
1335
  /** 今天的合计。 */
@@ -1587,6 +1615,13 @@ interface Config {
1587
1615
  showCreditDock?: boolean;
1588
1616
  /** 等上游开流时那行「排队第 N 位」是否显示。缺省 = 显示。 */
1589
1617
  showQueueDock?: boolean;
1618
+ /**
1619
+ * 是否在 DSH 侧边栏的图标列里加一个 CodeArts 图标(点开在中间列显示整页版卡片)。
1620
+ *
1621
+ * 缺省 = **不显示**:图标列是全宿主共用的资源,每个插件都默认占一格很快就会挤满,
1622
+ * 而「设置 → 插件」里本来就能看到同一张卡片,所以这一格交给用户自己决定要不要。
1623
+ */
1624
+ showSidebarPanel?: boolean;
1590
1625
  }
1591
1626
  declare const Config: z<Config>;
1592
1627
  declare function apply(ctx: Context, rawConfig: Config): void;
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as upstreamErrorMessage, B as modelUnavailableReason, C as isWelfareClaimedStatus, D as prepareChatBody, E as pickDailyWelfareCampaign, F as filterEnabledModels, G as codeartsAuthPath, H as CodeArtsCredentialStore, I as forgetModelUnavailable, K as startLogin, L as isBenefitBlocked, M as FALLBACK_CODEARTS_MODELS, N as clearBenefitBlocked, O as rateLimitFrame, P as creditRatioLabel, R as isBenefitModel, S as isTpmLimit, T as modelUnavailableReasonText, U as MANUAL_ACCOUNT_ID, V as CodeArtsAccountStore, W as codeartsAccountsPath, _ as isIamTokenError, a as SNAP_HOST, b as isParamInvalidFrame, c as claimDailyWelfare, d as fetchCallerIdentity, f as fetchModels, g as isConcurrencyCap, h as firstFrameError, i as refreshCatalog, j as CodeArtsCatalog, k as signedGet, l as clearQueueStatus, m as fetchWelfareCampaigns, n as codeartsUsagePath, o as canonicalModel, p as fetchQueueStatus, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as degradeTokenCeiling, v as isModelNotAccessible, w as maxTokensCeilingFor, x as isQueueableRate, y as isNotMultimodal, z as markModelUnavailable } from "./usage-CzJjkqOp.mjs";
1
+ import { A as upstreamErrorMessage, B as modelUnavailableReason, C as isWelfareClaimedStatus, D as prepareChatBody, E as pickDailyWelfareCampaign, F as filterEnabledModels, G as codeartsAuthPath, H as CodeArtsCredentialStore, I as forgetModelUnavailable, K as startLogin, L as isBenefitBlocked, M as FALLBACK_CODEARTS_MODELS, N as clearBenefitBlocked, O as rateLimitFrame, P as creditRatioLabel, R as isBenefitModel, S as isTpmLimit, T as modelUnavailableReasonText, U as MANUAL_ACCOUNT_ID, V as CodeArtsAccountStore, W as codeartsAccountsPath, _ as isIamTokenError, a as SNAP_HOST, b as isParamInvalidFrame, c as claimDailyWelfare, d as fetchCallerIdentity, f as fetchModels, g as isConcurrencyCap, h as firstFrameError, i as refreshCatalog, j as CodeArtsCatalog, k as signedGet, l as clearQueueStatus, m as fetchWelfareCampaigns, n as codeartsUsagePath, o as canonicalModel, p as fetchQueueStatus, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as degradeTokenCeiling, v as isModelNotAccessible, w as maxTokensCeilingFor, x as isQueueableRate, y as isNotMultimodal, z as markModelUnavailable } from "./usage-DGIuT79_.mjs";
2
2
  import { i as lazyApi, r as createProvider } from "./models-CgKCibpk.mjs";
3
3
  import z from "@deepseek-ai/schemastery";
4
4
  import { randomBytes, timingSafeEqual } from "node:crypto";
@@ -962,6 +962,13 @@ const recentShapes = [];
962
962
  function recentRequestShapes() {
963
963
  return [...recentShapes].reverse();
964
964
  }
965
+ const recentUsages = [];
966
+ /** 探针只留最近 5 帧:够看形状,又不至于把内存堆成日志。 */
967
+ const USAGE_PROBE_LIMIT = 5;
968
+ /** 最近的 usage 原帧,最新在前。 */
969
+ function recentUsageShapes() {
970
+ return [...recentUsages].reverse();
971
+ }
965
972
  function noteRequestShape(raw, model) {
966
973
  let parsed;
967
974
  try {
@@ -1406,7 +1413,16 @@ function createCodeArtsShim(options) {
1406
1413
  if (payload === "" || payload === "[DONE]") continue;
1407
1414
  try {
1408
1415
  const parsed = JSON.parse(payload);
1409
- if (parsed.usage !== void 0 && parsed.usage !== null) usage.noteUsage(attribution.accountId, attribution.model, parsed.usage);
1416
+ if (parsed.usage !== void 0 && parsed.usage !== null) {
1417
+ usage.noteUsage(attribution.accountId, attribution.model, parsed.usage);
1418
+ recentUsages.push({
1419
+ at: Date.now(),
1420
+ accountId: attribution.accountId,
1421
+ model: attribution.model,
1422
+ usage: parsed.usage
1423
+ });
1424
+ while (recentUsages.length > USAGE_PROBE_LIMIT) recentUsages.shift();
1425
+ }
1410
1426
  } catch {}
1411
1427
  }
1412
1428
  }
@@ -1511,10 +1527,14 @@ function modelBadgesOf(models) {
1511
1527
  };
1512
1528
  });
1513
1529
  }
1514
- /** 开关的默认值:没有 settings provider(或老配置没这两个键)时保持原有行为 —— 都显示。 */
1530
+ /**
1531
+ * 开关的默认值:没有 settings provider(或老配置没这两个键)时保持原有行为 ——
1532
+ * 底部两行都显示;侧边栏相反,缺省不开(那一格是所有插件共用的全局座位)。
1533
+ */
1515
1534
  const DEFAULT_DOCK_FLAGS = {
1516
1535
  credits: true,
1517
- queue: true
1536
+ queue: true,
1537
+ sidebar: false
1518
1538
  };
1519
1539
  /**
1520
1540
  * 把逐账号的领取结果汇总成一条:HTTP 层只看 ok,卡片看 results 逐号展示。
@@ -1619,7 +1639,9 @@ function usageOf(deps) {
1619
1639
  requests: 0,
1620
1640
  promptTokens: 0,
1621
1641
  completionTokens: 0,
1622
- totalTokens: 0
1642
+ totalTokens: 0,
1643
+ cachedTokens: 0,
1644
+ reasoningTokens: 0
1623
1645
  };
1624
1646
  return {
1625
1647
  today: snapshot?.today ?? noTotals,
@@ -1743,9 +1765,13 @@ function dockHandler(deps) {
1743
1765
  }
1744
1766
  try {
1745
1767
  const body = JSON.parse(await readBody(req));
1746
- const touched = ["credits", "queue"].filter((key) => body[key] !== void 0);
1768
+ const touched = [
1769
+ "credits",
1770
+ "queue",
1771
+ "sidebar"
1772
+ ].filter((key) => body[key] !== void 0);
1747
1773
  if (touched.length === 0 || touched.some((key) => typeof body[key] !== "boolean")) {
1748
- json(res, 400, { error: "expected {\"credits\"?: boolean, \"queue\"?: boolean}" });
1774
+ json(res, 400, { error: "expected {\"credits\"?: boolean, \"queue\"?: boolean, \"sidebar\"?: boolean}" });
1749
1775
  return;
1750
1776
  }
1751
1777
  if (!await deps.setDock(Object.fromEntries(touched.map((key) => [key, body[key]])))) {
@@ -2129,7 +2155,10 @@ function debugHandler(deps) {
2129
2155
  json(res, 403, { error: "request-not-trusted" });
2130
2156
  return;
2131
2157
  }
2132
- json(res, 200, { requests: deps.recentRequests?.() ?? [] });
2158
+ json(res, 200, {
2159
+ requests: deps.recentRequests?.() ?? [],
2160
+ usages: deps.recentUsages?.() ?? []
2161
+ });
2133
2162
  };
2134
2163
  }
2135
2164
  /** Mount the status / models / token routes. */
@@ -2383,7 +2412,8 @@ function unwrapConfig(value) {
2383
2412
  const Config = z.object({
2384
2413
  enabledModels: z.array(z.string()).description("Model ids offered in the model pickers (empty means every model)").volatile(),
2385
2414
  showCreditDock: z.boolean().default(true).description("Show the remaining-credits line under the chat input box").volatile(),
2386
- showQueueDock: z.boolean().default(true).description("Show the upstream queue position under the chat input box").volatile()
2415
+ showQueueDock: z.boolean().default(true).description("Show the upstream queue position under the chat input box").volatile(),
2416
+ showSidebarPanel: z.boolean().default(false).description("Show a CodeArts icon in the DSH sidebar icon column, opening this page in the middle column").volatile()
2387
2417
  });
2388
2418
  function settingsCompatNamespace(ctx) {
2389
2419
  return resolveSettingsNamespaceCompat(ctx, ctx.get("settings"), CODEARTS_SETTINGS_NS);
@@ -2458,14 +2488,16 @@ function apply(ctx, rawConfig) {
2458
2488
  };
2459
2489
  const dock = () => ({
2460
2490
  credits: current().showCreditDock !== false,
2461
- queue: current().showQueueDock !== false
2491
+ queue: current().showQueueDock !== false,
2492
+ sidebar: current().showSidebarPanel === true
2462
2493
  });
2463
2494
  const setDock = async (patch) => {
2464
2495
  const settings = ctx.get("settings");
2465
2496
  if (settings === void 0) return false;
2466
2497
  await settings.update(settingsCompatNamespace(ctx), {
2467
2498
  ...patch.credits === void 0 ? {} : { showCreditDock: patch.credits },
2468
- ...patch.queue === void 0 ? {} : { showQueueDock: patch.queue }
2499
+ ...patch.queue === void 0 ? {} : { showQueueDock: patch.queue },
2500
+ ...patch.sidebar === void 0 ? {} : { showSidebarPanel: patch.sidebar }
2469
2501
  });
2470
2502
  return true;
2471
2503
  };
@@ -2481,6 +2513,7 @@ function apply(ctx, rawConfig) {
2481
2513
  usage: () => usage.snapshot(),
2482
2514
  queue: () => queue.snapshot(),
2483
2515
  recentRequests: () => recentRequestShapes(),
2516
+ recentUsages: () => recentUsageShapes(),
2484
2517
  credits: (signal) => loadCredits(signal),
2485
2518
  refreshCredits: (signal) => {
2486
2519
  credits.invalidate();
@@ -207,6 +207,41 @@ function parseToken(json, dpopJWK, clientId, prev) {
207
207
  dpopPrivateKey: dpopJWK
208
208
  };
209
209
  }
210
+ /**
211
+ * 华为登录后浏览器落回本机回调的落地页。
212
+ *
213
+ * 这一页本身没有任何后续逻辑:`code` 在响应发出的同一时刻就已交给等待队列
214
+ * (见 startLogin 里的 finish),token 交换完全在服务端做,页面关不关都不影响
215
+ * 登录结果。它只是 redirect_uri 必然要渲染的一个落点 —— 华为侧没有「登录成功」
216
+ * 页可停留,成功标志就是这一次 302 到 127.0.0.1。
217
+ *
218
+ * 卡片用 `window.open` 打开授权页,所以这个标签页能自己关掉;用户从卡片的
219
+ * 手动链接打开、或浏览器拦脚本关窗时 close 会失败,1.2s 后把提示换成「可关闭」。
220
+ */
221
+ const CALLBACK_HTML = `<!doctype html>
222
+ <html lang="zh-CN">
223
+ <head>
224
+ <meta charset="utf-8">
225
+ <meta name="viewport" content="width=device-width,initial-scale=1">
226
+ <title>CodeArts 登录</title>
227
+ <style>
228
+ html,body{height:100%}
229
+ body{margin:0;display:flex;align-items:center;justify-content:center;background:#fff;color:#1f2329;
230
+ font:15px/1.6 system-ui,-apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}
231
+ p{margin:0;padding:24px;text-align:center}
232
+ </style>
233
+ </head>
234
+ <body>
235
+ <p id="msg">正在返回 DeepSeek Harness…</p>
236
+ <script>
237
+ setTimeout(function () { try { window.close() } catch (e) {} }, 300)
238
+ setTimeout(function () {
239
+ var el = document.getElementById('msg')
240
+ if (el) el.textContent = 'CodeArts 登录成功,本页可关闭,请回到 DeepSeek Harness。'
241
+ }, 1200)
242
+ <\/script>
243
+ </body>
244
+ </html>`;
210
245
  async function startLogin(cfg = defaultLoginConfig()) {
211
246
  const ticketId = randomHex(16);
212
247
  const { verifier, challenge, method } = pkce();
@@ -236,8 +271,11 @@ async function startLogin(cfg = defaultLoginConfig()) {
236
271
  }
237
272
  finish(code);
238
273
  function finish(c) {
239
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
240
- res.end("<h3>CodeArts 登录成功,可关闭此页面。</h3>");
274
+ res.writeHead(200, {
275
+ "Content-Type": "text/html; charset=utf-8",
276
+ "Cache-Control": "no-store"
277
+ });
278
+ res.end(CALLBACK_HTML);
241
279
  if (c) for (const w of waiters.splice(0)) w(c);
242
280
  }
243
281
  });
@@ -2709,7 +2747,9 @@ function emptyTotals() {
2709
2747
  requests: 0,
2710
2748
  promptTokens: 0,
2711
2749
  completionTokens: 0,
2712
- totalTokens: 0
2750
+ totalTokens: 0,
2751
+ cachedTokens: 0,
2752
+ reasoningTokens: 0
2713
2753
  };
2714
2754
  }
2715
2755
  function addTotals(into, add) {
@@ -2717,6 +2757,8 @@ function addTotals(into, add) {
2717
2757
  into.promptTokens += add.promptTokens ?? 0;
2718
2758
  into.completionTokens += add.completionTokens ?? 0;
2719
2759
  into.totalTokens += add.totalTokens ?? 0;
2760
+ into.cachedTokens += add.cachedTokens ?? 0;
2761
+ into.reasoningTokens += add.reasoningTokens ?? 0;
2720
2762
  }
2721
2763
  function mergeTotals(into, from) {
2722
2764
  addTotals(into, from);
@@ -2730,12 +2772,41 @@ function localDateKey(at = Date.now()) {
2730
2772
  function positiveInt(value) {
2731
2773
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
2732
2774
  }
2733
- /** 从上游 usage 帧里取三个计数字段(缺哪个算 0,total 缺失时用 prompt+completion 兜底)。 */
2775
+ /**
2776
+ * 从上游 usage 帧里取缓存命中数。
2777
+ *
2778
+ * 位置见过两种:OpenAI 的 `prompt_tokens_details.cached_tokens`,以及兼容层直接
2779
+ * 平铺在顶层的几种别名。取第一个能读到的。缓存输入必然是输入的子集,所以比
2780
+ * `promptTokens` 还大的脏数据直接钳掉 —— 否则界面上会出现 >100% 的命中率。
2781
+ */
2782
+ function cachedTokensOf(raw, promptTokens) {
2783
+ if (promptTokens <= 0) return 0;
2784
+ const cached = positiveInt(nestedField(raw.prompt_tokens_details, "cached_tokens") ?? raw.cached_tokens ?? raw.cache_tokens ?? raw.prompt_cache_hit_tokens);
2785
+ return Math.min(cached, promptTokens);
2786
+ }
2787
+ /**
2788
+ * 从上游 usage 帧里取思考 token。
2789
+ *
2790
+ * 口径与 {@link cachedTokensOf} 对称:嵌套 `completion_tokens_details.reasoning_tokens`
2791
+ * 优先,顶层 `reasoning_tokens` 兜底,超过输出总量就钳掉(它含在输出里)。
2792
+ */
2793
+ function reasoningTokensOf(raw, completionTokens) {
2794
+ if (completionTokens <= 0) return 0;
2795
+ const reasoning = positiveInt(nestedField(raw.completion_tokens_details, "reasoning_tokens") ?? raw.reasoning_tokens);
2796
+ return Math.min(reasoning, completionTokens);
2797
+ }
2798
+ /** 读 `*_tokens_details` 这类嵌套对象里的一个字段;不是对象(或缺字段)给 undefined。 */
2799
+ function nestedField(details, key) {
2800
+ return typeof details === "object" && details !== null ? details[key] : void 0;
2801
+ }
2802
+ /** 从上游 usage 帧里取计数(缺哪个算 0,total 缺失时用 prompt+completion 兜底)。 */
2734
2803
  function normalizeUsage(raw) {
2735
2804
  if (raw === void 0) return {
2736
2805
  promptTokens: 0,
2737
2806
  completionTokens: 0,
2738
- totalTokens: 0
2807
+ totalTokens: 0,
2808
+ cachedTokens: 0,
2809
+ reasoningTokens: 0
2739
2810
  };
2740
2811
  const promptTokens = positiveInt(raw.prompt_tokens);
2741
2812
  const completionTokens = positiveInt(raw.completion_tokens);
@@ -2743,9 +2814,31 @@ function normalizeUsage(raw) {
2743
2814
  return {
2744
2815
  promptTokens,
2745
2816
  completionTokens,
2746
- totalTokens: total > 0 ? total : promptTokens + completionTokens
2817
+ totalTokens: total > 0 ? total : promptTokens + completionTokens,
2818
+ cachedTokens: cachedTokensOf(raw, promptTokens),
2819
+ reasoningTokens: reasoningTokensOf(raw, completionTokens)
2820
+ };
2821
+ }
2822
+ /** 读一项累计值,缺字段(旧账本没有 cachedTokens / reasoningTokens)就按 0 补齐。 */
2823
+ function normalizeTotals(raw) {
2824
+ return {
2825
+ requests: positiveInt(raw?.requests),
2826
+ promptTokens: positiveInt(raw?.promptTokens),
2827
+ completionTokens: positiveInt(raw?.completionTokens),
2828
+ totalTokens: positiveInt(raw?.totalTokens),
2829
+ cachedTokens: positiveInt(raw?.cachedTokens),
2830
+ reasoningTokens: positiveInt(raw?.reasoningTokens)
2747
2831
  };
2748
2832
  }
2833
+ /** 整张「模型/账号 -> 累计」表补齐字段。 */
2834
+ function normalizeStore(store) {
2835
+ const out = {};
2836
+ for (const [key, totals] of Object.entries(store ?? {})) {
2837
+ if (typeof totals !== "object" || totals === null) continue;
2838
+ out[key] = normalizeTotals(totals);
2839
+ }
2840
+ return out;
2841
+ }
2749
2842
  /**
2750
2843
  * 用量账本。
2751
2844
  *
@@ -2784,8 +2877,8 @@ var CodeArtsUsageTracker = class {
2784
2877
  if (parsed.version !== 1 || typeof parsed.days !== "object" || parsed.days === null) return;
2785
2878
  for (const [date, day] of Object.entries(parsed.days)) {
2786
2879
  if (typeof day !== "object" || day === null) continue;
2787
- const models = day.models ?? {};
2788
- const accounts = day.accounts ?? {};
2880
+ const models = normalizeStore(day.models);
2881
+ const accounts = normalizeStore(day.accounts);
2789
2882
  this.days.set(date, {
2790
2883
  models,
2791
2884
  accounts
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": "1.2.3",
5
+ "version": "1.2.5",
6
6
  "packageManager": "pnpm@11.24.0",
7
7
  "author": "elegant01",
8
8
  "keywords": [