dsh-codearts 0.5.3 → 1.0.1

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
@@ -443,6 +443,8 @@ interface UsageSnapshot {
443
443
  today: UsageTotals;
444
444
  /** 账本里所有保留天数的合计。 */
445
445
  allTime: UsageTotals;
446
+ /** 最近 30 天(含今天)的合计。 */
447
+ last30: UsageTotals;
446
448
  /** 今天按模型的合计,按 totalTokens 降序。 */
447
449
  byModelToday: {
448
450
  id: string;
@@ -646,22 +648,23 @@ interface CreditCacheOptions {
646
648
  * 而上游对同一账号的并发是有成本/风险的。
647
649
  */
648
650
  declare class CodeArtsCreditCache {
649
- private state;
650
- private inflight;
651
+ /** 按账号分槽:多账号下每个号有自己的余额,不能共用一个快照。 */
652
+ private readonly states;
653
+ private readonly inflights;
651
654
  private readonly ttlMs;
652
655
  private readonly failureTtlMs;
653
656
  private readonly load;
654
657
  private readonly now;
655
658
  constructor(options?: CreditCacheOptions);
656
- /** 当前缓存内容(不触发请求),供只想读快照的场景使用。 */
657
- peek(): CodeArtsCreditState | undefined;
658
- /** 丢掉缓存,下次 get 会重新拉(重新登录/添加账号后调用)。 */
659
- invalidate(): void;
659
+ /** 某账号的缓存内容(不触发请求),供只想读快照的场景使用。 */
660
+ peek(accountId: string): CodeArtsCreditState | undefined;
661
+ /** 丢掉缓存,下次 get 会重新拉(重新登录/添加账号后调用)。省略账号则清空全部。 */
662
+ invalidate(accountId?: string): void;
660
663
  /**
661
- * 取一次积分。命中缓存直接返回;否则合并成一次上游请求。
664
+ * 取一次某账号的积分。命中缓存直接返回;否则合并成一次上游请求。
662
665
  * **不抛异常**:失败以 `phase: 'error'` 返回,调用方(状态路由)照样能渲染。
663
666
  */
664
- get(credential: CodeArtsResolved, signal?: AbortSignal): Promise<CodeArtsCreditState>;
667
+ get(accountId: string, credential: CodeArtsResolved, signal?: AbortSignal): Promise<CodeArtsCreditState>;
665
668
  private run;
666
669
  }
667
670
  //#endregion
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as isBenefitModel, C as upstreamErrorMessage, D as creditRatioLabel, E as clearBenefitBlocked, F as startLogin, M as CodeArtsCredentialStore, N as codeartsAccountsPath, O as filterEnabledModels, P as codeartsAuthPath, S as signedGet, T as FALLBACK_CODEARTS_MODELS, _ as isWelfareClaimedStatus, a as SNAP_HOST, b as prepareChatBody, c as claimDailyWelfare, d as fetchModels, f as fetchWelfareCampaigns, g as isTpmLimit, h as isQueueableRate, i as refreshCatalog, j as CodeArtsAccountStore, k as isBenefitBlocked, l as degradeTokenCeiling, m as isParamInvalidFrame, n as codeartsUsagePath, o as canonicalModel, p as isConcurrencyCap, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as fetchCallerIdentity, v as maxTokensCeilingFor, w as CodeArtsCatalog, x as rateLimitFrame, y as pickDailyWelfareCampaign } from "./usage-Djtou78p.mjs";
1
+ import { A as isBenefitModel, C as upstreamErrorMessage, D as creditRatioLabel, E as clearBenefitBlocked, F as startLogin, M as CodeArtsCredentialStore, N as codeartsAccountsPath, O as filterEnabledModels, P as codeartsAuthPath, S as signedGet, T as FALLBACK_CODEARTS_MODELS, _ as isWelfareClaimedStatus, a as SNAP_HOST, b as prepareChatBody, c as claimDailyWelfare, d as fetchModels, f as fetchWelfareCampaigns, g as isTpmLimit, h as isQueueableRate, i as refreshCatalog, j as CodeArtsAccountStore, k as isBenefitBlocked, l as degradeTokenCeiling, m as isParamInvalidFrame, n as codeartsUsagePath, o as canonicalModel, p as isConcurrencyCap, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as fetchCallerIdentity, v as maxTokensCeilingFor, w as CodeArtsCatalog, x as rateLimitFrame, y as pickDailyWelfareCampaign } from "./usage-BEoMNRwo.mjs";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { createServer } from "node:http";
@@ -314,8 +314,9 @@ function safeJson(text) {
314
314
  * 而上游对同一账号的并发是有成本/风险的。
315
315
  */
316
316
  var CodeArtsCreditCache = class {
317
- state;
318
- inflight;
317
+ /** 按账号分槽:多账号下每个号有自己的余额,不能共用一个快照。 */
318
+ states = /* @__PURE__ */ new Map();
319
+ inflights = /* @__PURE__ */ new Map();
319
320
  ttlMs;
320
321
  failureTtlMs;
321
322
  load;
@@ -326,37 +327,46 @@ var CodeArtsCreditCache = class {
326
327
  this.load = options.load ?? fetchCredits;
327
328
  this.now = options.now ?? (() => Date.now());
328
329
  }
329
- /** 当前缓存内容(不触发请求),供只想读快照的场景使用。 */
330
- peek() {
331
- return this.state;
330
+ /** 某账号的缓存内容(不触发请求),供只想读快照的场景使用。 */
331
+ peek(accountId) {
332
+ return this.states.get(accountId);
332
333
  }
333
- /** 丢掉缓存,下次 get 会重新拉(重新登录/添加账号后调用)。 */
334
- invalidate() {
335
- this.state = void 0;
336
- this.inflight = void 0;
334
+ /** 丢掉缓存,下次 get 会重新拉(重新登录/添加账号后调用)。省略账号则清空全部。 */
335
+ invalidate(accountId) {
336
+ if (accountId === void 0) {
337
+ this.states.clear();
338
+ this.inflights.clear();
339
+ return;
340
+ }
341
+ this.states.delete(accountId);
342
+ this.inflights.delete(accountId);
337
343
  }
338
344
  /**
339
- * 取一次积分。命中缓存直接返回;否则合并成一次上游请求。
345
+ * 取一次某账号的积分。命中缓存直接返回;否则合并成一次上游请求。
340
346
  * **不抛异常**:失败以 `phase: 'error'` 返回,调用方(状态路由)照样能渲染。
341
347
  */
342
- async get(credential, signal) {
343
- const cached = this.state;
348
+ async get(accountId, credential, signal) {
349
+ const cached = this.states.get(accountId);
344
350
  if (cached !== void 0) {
345
351
  const ttl = cached.phase === "ok" ? this.ttlMs : this.failureTtlMs;
346
352
  if (this.now() - cached.at < ttl) return cached;
347
353
  }
348
- return this.inflight ??= this.run(credential, signal).finally(() => {
349
- this.inflight = void 0;
354
+ const existing = this.inflights.get(accountId);
355
+ if (existing !== void 0) return existing;
356
+ const inflight = this.run(accountId, credential, signal).finally(() => {
357
+ this.inflights.delete(accountId);
350
358
  });
359
+ this.inflights.set(accountId, inflight);
360
+ return inflight;
351
361
  }
352
- async run(credential, signal) {
362
+ async run(accountId, credential, signal) {
353
363
  try {
354
364
  const state = {
355
365
  phase: "ok",
356
366
  value: await this.load(credential, signal),
357
367
  at: this.now()
358
368
  };
359
- this.state = state;
369
+ this.states.set(accountId, state);
360
370
  return state;
361
371
  } catch (error) {
362
372
  const state = {
@@ -364,7 +374,7 @@ var CodeArtsCreditCache = class {
364
374
  message: error instanceof Error ? error.message : String(error),
365
375
  at: this.now()
366
376
  };
367
- this.state = state;
377
+ this.states.set(accountId, state);
368
378
  return state;
369
379
  }
370
380
  }
@@ -1094,6 +1104,23 @@ function choicesOf(deps) {
1094
1104
  };
1095
1105
  });
1096
1106
  }
1107
+ /**
1108
+ * 把逐账号的领取结果汇总成一条:HTTP 层只看 ok,卡片看 results 逐号展示。
1109
+ * 有一个号失败就算整批 ok=false,但别的号领到的结果照样在 results 里。
1110
+ */
1111
+ function summarizeClaims(results) {
1112
+ const failed = results.find((result) => result.state === "failed");
1113
+ const claimed = results.filter((result) => result.claimed);
1114
+ const messages = [...new Set(results.map((result) => result.message).filter((message) => message !== ""))];
1115
+ return {
1116
+ ok: results.length > 0 && failed === void 0,
1117
+ claimed: claimed.length > 0,
1118
+ state: claimed.length > 0 ? "claimed" : failed?.state ?? results[0]?.state ?? "failed",
1119
+ amount: claimed.reduce((sum, result) => sum + result.amount, 0),
1120
+ message: results.length === 1 ? results[0].message : messages.join("; ").slice(0, 300),
1121
+ results
1122
+ };
1123
+ }
1097
1124
  function json(res, status, body) {
1098
1125
  const payload = JSON.stringify(body);
1099
1126
  res.writeHead(status, {
@@ -1185,7 +1212,9 @@ function usageOf(deps) {
1185
1212
  return {
1186
1213
  today: snapshot?.today ?? noTotals,
1187
1214
  allTime: snapshot?.allTime ?? noTotals,
1215
+ last30: snapshot?.last30 ?? noTotals,
1188
1216
  byModelToday: snapshot?.byModelToday ?? [],
1217
+ byAccountToday: snapshot?.byAccountToday ?? {},
1189
1218
  days: snapshot?.days ?? 0,
1190
1219
  ...snapshot?.lastAt === void 0 ? {} : { lastAt: snapshot.lastAt },
1191
1220
  slots: slotsOf(deps)
@@ -1200,8 +1229,13 @@ function usageOf(deps) {
1200
1229
  async function creditsOf(deps) {
1201
1230
  if (deps.credits === void 0) return {};
1202
1231
  try {
1203
- const state = await deps.credits();
1204
- return state.phase === "ok" ? { credits: state.value } : { creditsError: state.message };
1232
+ const entries = await deps.credits();
1233
+ const primary = entries.find((entry) => entry.credits !== void 0) ?? entries[0];
1234
+ return {
1235
+ ...primary?.credits === void 0 ? {} : { credits: primary.credits },
1236
+ ...primary?.credits === void 0 && primary?.creditsError !== void 0 ? { creditsError: primary.creditsError } : {},
1237
+ ...entries.length === 0 ? {} : { creditsByAccount: entries }
1238
+ };
1205
1239
  } catch (error) {
1206
1240
  return { creditsError: safeMessage(error) };
1207
1241
  }
@@ -1413,8 +1447,17 @@ function claimHandler(deps) {
1413
1447
  json(res, 403, { error: "request-not-trusted" });
1414
1448
  return;
1415
1449
  }
1450
+ let accountId;
1451
+ const type = req.headers["content-type"];
1452
+ if (typeof type === "string" && type.trim().toLowerCase().startsWith("application/json")) try {
1453
+ const body = JSON.parse(await readBody(req));
1454
+ if (typeof body?.id === "string" && body.id.trim() !== "") accountId = body.id.trim();
1455
+ } catch {
1456
+ json(res, 400, { error: "expected {\"id\"?: string}" });
1457
+ return;
1458
+ }
1416
1459
  try {
1417
- const result = await deps.claim();
1460
+ const result = await deps.claim(accountId);
1418
1461
  json(res, result.ok ? 200 : 500, result);
1419
1462
  } catch (error) {
1420
1463
  json(res, 500, {
@@ -1701,38 +1744,50 @@ function apply(ctx, rawConfig) {
1701
1744
  /** /status 是卡片与对话底部都会轮询的接口,积分慢请求不能把它拖成转圈。 */
1702
1745
  const CREDIT_TIMEOUT_MS = 8e3;
1703
1746
  /**
1704
- * 拉一次积分。
1747
+ * 拉一次积分(每个账号各自一份)。
1705
1748
  *
1706
- * 积分是**账号级**属性,不随请求轮转:这里挑第一个没用坏的账号,够覆盖正常用法
1707
- * (多账号下的积分差异不在本插件关心范围内)。
1749
+ * 积分是**账号级**属性:多账号下每个号的余额、到期、签到状态都不同,所以逐号
1750
+ * 并发拉、逐号缓存(缓存键就是账号 id)。一个号失败只影响它自己那一份。
1708
1751
  */
1709
1752
  const loadCredits = async (signal) => {
1710
1753
  const ids = await store.ids();
1711
- if (ids.length === 0) return {
1712
- phase: "error",
1713
- message: "not signed in",
1714
- at: Date.now()
1715
- };
1754
+ if (ids.length === 0) return [];
1716
1755
  const controller = new AbortController();
1717
1756
  const timer = setTimeout(() => controller.abort(), CREDIT_TIMEOUT_MS);
1718
1757
  timer.unref?.();
1719
1758
  signal?.addEventListener("abort", () => controller.abort(), { once: true });
1720
1759
  try {
1721
- let last;
1722
- for (const id of ids) {
1760
+ return await Promise.all(ids.map(async (id) => {
1723
1761
  const stored = await store.credential(id);
1724
- if (stored === void 0 || stored.stale === true) continue;
1725
- const resolved = await store.resolve(id);
1726
- if (resolved === void 0) continue;
1727
- const state = await credits.get(resolved, controller.signal);
1728
- if (state.phase === "ok") return state;
1729
- last = state;
1730
- }
1731
- return last ?? {
1732
- phase: "error",
1733
- message: "no usable credential",
1734
- at: Date.now()
1735
- };
1762
+ if (stored === void 0) return {
1763
+ id,
1764
+ creditsError: "account no longer stored"
1765
+ };
1766
+ if (stored.stale === true) return {
1767
+ id,
1768
+ creditsError: "account needs sign-in"
1769
+ };
1770
+ try {
1771
+ const resolved = await store.resolve(id);
1772
+ if (resolved === void 0) return {
1773
+ id,
1774
+ creditsError: "no usable credential"
1775
+ };
1776
+ const state = await credits.get(id, resolved, controller.signal);
1777
+ return state.phase === "ok" ? {
1778
+ id,
1779
+ credits: state.value
1780
+ } : {
1781
+ id,
1782
+ creditsError: state.message
1783
+ };
1784
+ } catch (error) {
1785
+ return {
1786
+ id,
1787
+ creditsError: error instanceof Error ? error.message : String(error)
1788
+ };
1789
+ }
1790
+ }));
1736
1791
  } finally {
1737
1792
  clearTimeout(timer);
1738
1793
  }
@@ -1755,40 +1810,62 @@ function apply(ctx, rawConfig) {
1755
1810
  pool: () => pool.list(),
1756
1811
  usage: () => usage.snapshot(),
1757
1812
  credits: (signal) => loadCredits(signal),
1758
- claim: async () => {
1759
- try {
1760
- const ids = await store.ids();
1761
- for (const id of ids) {
1762
- const stored = await store.credential(id);
1763
- if (stored === void 0 || stored.stale === true) continue;
1764
- const resolved = await store.resolve(id);
1765
- if (resolved === void 0) continue;
1766
- const outcome = await claimDailyWelfare(resolved, AbortSignal.timeout(2e4));
1767
- if (outcome.state === "claimed") credits.invalidate();
1768
- return {
1769
- ok: outcome.state !== "failed",
1770
- claimed: outcome.state === "claimed",
1771
- state: outcome.state,
1772
- amount: outcome.amount,
1773
- message: outcome.message
1774
- };
1813
+ claim: async (accountId) => {
1814
+ const ids = accountId === void 0 ? await store.ids() : [accountId];
1815
+ if (ids.length === 0) return {
1816
+ ok: false,
1817
+ claimed: false,
1818
+ state: "failed",
1819
+ amount: 0,
1820
+ message: "no usable credential"
1821
+ };
1822
+ const results = [];
1823
+ for (const id of ids) try {
1824
+ const stored = await store.credential(id);
1825
+ if (stored === void 0 || stored.stale === true) {
1826
+ results.push({
1827
+ id,
1828
+ ok: false,
1829
+ claimed: false,
1830
+ state: "failed",
1831
+ amount: 0,
1832
+ message: stored === void 0 ? "account not found" : "account needs sign-in"
1833
+ });
1834
+ continue;
1775
1835
  }
1776
- return {
1777
- ok: false,
1778
- claimed: false,
1779
- state: "failed",
1780
- amount: 0,
1781
- message: "no usable credential"
1782
- };
1836
+ const resolved = await store.resolve(id);
1837
+ if (resolved === void 0) {
1838
+ results.push({
1839
+ id,
1840
+ ok: false,
1841
+ claimed: false,
1842
+ state: "failed",
1843
+ amount: 0,
1844
+ message: "no usable credential"
1845
+ });
1846
+ continue;
1847
+ }
1848
+ const outcome = await claimDailyWelfare(resolved, AbortSignal.timeout(2e4));
1849
+ if (outcome.state === "claimed") credits.invalidate(id);
1850
+ results.push({
1851
+ id,
1852
+ ok: outcome.state !== "failed",
1853
+ claimed: outcome.state === "claimed",
1854
+ state: outcome.state,
1855
+ amount: outcome.amount,
1856
+ message: outcome.message
1857
+ });
1783
1858
  } catch (error) {
1784
- return {
1859
+ results.push({
1860
+ id,
1785
1861
  ok: false,
1786
1862
  claimed: false,
1787
1863
  state: "failed",
1788
1864
  amount: 0,
1789
1865
  message: error instanceof Error ? error.message : String(error)
1790
- };
1866
+ });
1791
1867
  }
1868
+ return summarizeClaims(results);
1792
1869
  },
1793
1870
  refreshModels: async () => {
1794
1871
  const outcome = await refreshCatalog(store, catalog, AbortSignal.timeout(MANUAL_REFRESH_MS));
@@ -2528,8 +2528,13 @@ var CodeArtsUsageTracker = class {
2528
2528
  snapshot(at = this.now()) {
2529
2529
  const todayKey = localDateKey(at);
2530
2530
  const today = this.days.get(todayKey);
2531
+ const cutoff = localDateKey(at - 2592e6);
2531
2532
  const allTime = emptyTotals();
2532
- 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
+ }
2533
2538
  const models = today?.models ?? {};
2534
2539
  return {
2535
2540
  today: {
@@ -2537,6 +2542,7 @@ var CodeArtsUsageTracker = class {
2537
2542
  ...today === void 0 ? {} : sumOf(models)
2538
2543
  },
2539
2544
  allTime,
2545
+ last30,
2540
2546
  byModelToday: Object.entries(models).map(([id, totals]) => ({
2541
2547
  id,
2542
2548
  totals: { ...totals }
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.3",
5
+ "version": "1.0.1",
6
6
  "packageManager": "pnpm@11.24.0",
7
7
  "author": "elegant01",
8
8
  "keywords": [
@@ -50,6 +50,7 @@
50
50
  "cordis.patch.yml",
51
51
  "README.md",
52
52
  "README.en.md",
53
+ "CHANGELOG.md",
53
54
  "INSTALL.md",
54
55
  "INSTALL.en.md",
55
56
  "DEV.md",