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/CHANGELOG.md +96 -0
- package/README.en.md +4 -0
- package/README.md +6 -2
- package/lib/bin.mjs +2 -2
- package/lib/client/index.d.cts +17 -5
- package/lib/client/index.js +746 -397
- package/lib/index.d.mts +11 -8
- package/lib/index.mjs +146 -69
- package/lib/{usage-Djtou78p.mjs → usage-BEoMNRwo.mjs} +7 -1
- package/package.json +2 -1
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
|
-
|
|
650
|
-
private
|
|
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-
|
|
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
|
-
|
|
318
|
-
|
|
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.
|
|
330
|
+
/** 某账号的缓存内容(不触发请求),供只想读快照的场景使用。 */
|
|
331
|
+
peek(accountId) {
|
|
332
|
+
return this.states.get(accountId);
|
|
332
333
|
}
|
|
333
|
-
/** 丢掉缓存,下次 get
|
|
334
|
-
invalidate() {
|
|
335
|
-
|
|
336
|
-
|
|
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.
|
|
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
|
-
|
|
349
|
-
|
|
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.
|
|
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.
|
|
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
|
|
1204
|
-
|
|
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
|
-
|
|
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
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
if (
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
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
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
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
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
"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",
|