dsh-codearts 0.4.0 → 0.4.2

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/DEV.md CHANGED
@@ -40,6 +40,35 @@ pnpm run hmr:off # 关掉,回到 base 默认(只监听 profile 配置
40
40
 
41
41
  改完这个 patch 通常不用重启:profile 的 patch 文件本身也在 HMR 的监听范围内(profile 里 `patchReload: live`),条目会重新加载。若发现没生效,重启一次 DSH 即可,之后就一直有效。
42
42
 
43
+ > ⚠️ **开了模块监听就别用插件管理器/npm 升级本插件。** 宿主为热重载监视着
44
+ > `node_modules/dsh-codearts`,目录被占用后 pnpm 替换版本时会失败:
45
+ >
46
+ > ```
47
+ > [ERR_PNPM_EPERM] [importPackage ...\node_modules\dsh-codearts]
48
+ > EPERM: operation not permitted, rename
49
+ > '...\dsh-codearts_tmp_20976_1' -> '...\dsh-codearts'
50
+ > ```
51
+ >
52
+ > 更麻烦的是失败后**插件管理器会回滚**:把 `dsh-codearts` 从 profile 的
53
+ > `dependencies` 和 `bundles` 里一起删掉,表现为「插件列表里它没了」。升级前的正确顺序是
54
+ > `pnpm run hmr:off` → 升级 → (要接着开发再)`pnpm run hmr:on`。
55
+ >
56
+ > 这条只影响**跑过 `hmr:on` 的开发机**:该覆盖行写在 profile 的
57
+ > `cordis.patch.yml` 里,插件包自带的 patch 只有 `insert: llm-codearts`,
58
+ > 普通用户从 dshmarket 直接升级不会碰到。DSH base 的 desktop bundle 里默认是
59
+ > **不监视模块**的(原文,`app.asar` 内 base 的 patch):
60
+ >
61
+ > ```yaml
62
+ > # Profile configuration reloads by default; module roots are opt-in.
63
+ > - id: hmr
64
+ > name: '@deepseek-ai/dsh-hmr'
65
+ > disabled: !!js "!ctx.get('profileContext')"
66
+ > config:
67
+ > root: []
68
+ > ```
69
+ >
70
+ > 即 `root` 默认空表 —— 没人去开它,就没有句柄占用 `node_modules/dsh-codearts`。
71
+
43
72
  ## 日常循环
44
73
 
45
74
  ```sh
package/lib/bin.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { C as codeartsAccountsPath, b as isBenefitModel, n as codeartsUsagePath, r as discoverModels, t as CodeArtsUsageTracker, u as fetchCallerIdentity, v as FALLBACK_CODEARTS_MODELS, x as CodeArtsAccountStore } from "./usage-3W88T_H1.mjs";
2
+ import { C as isBenefitModel, E as codeartsAccountsPath, n as codeartsUsagePath, r as discoverModels, t as CodeArtsUsageTracker, u as fetchCallerIdentity, w as CodeArtsAccountStore, x as FALLBACK_CODEARTS_MODELS } from "./usage-B4vQb1sP.mjs";
3
3
  import { existsSync, statSync } from "node:fs";
4
4
  //#region src/version.ts
5
- const CODEARTS_VERSION = "0.3.0";
5
+ const CODEARTS_VERSION = "0.4.2";
6
6
  //#endregion
7
7
  //#region src/bin.ts
8
8
  /**
@@ -68,8 +68,12 @@ interface CodeArtsSettingsKey {
68
68
  creditsDock: string;
69
69
  creditsDockTitle: string;
70
70
  claimButton: string;
71
- claiming: string;
72
- claimSuccess: string;
71
+ claimButtonDone: string;
72
+ claimPending: string;
73
+ claimHint: string;
74
+ claimDone: string;
75
+ claimAlready: string;
76
+ claimUnavailable: string;
73
77
  claimFailed: string;
74
78
  [key: string]: string;
75
79
  }
@@ -18,32 +18,32 @@ if (typeof document !== "undefined" && document.querySelector("style[data-plugin
18
18
  document.head.appendChild(tag);
19
19
  }
20
20
  var CodeArtsPluginCard_module_css_default = {
21
- "choices": "DZPfya_choices",
21
+ "name": "DZPfya_name",
22
22
  "actions": "DZPfya_actions",
23
23
  "choiceRow": "DZPfya_choiceRow",
24
- "header": "DZPfya_header",
25
24
  "manualSummary": "DZPfya_manualSummary",
26
25
  "field": "DZPfya_field",
27
26
  "meta": "DZPfya_meta",
27
+ "headActions": "DZPfya_headActions",
28
28
  "link": "DZPfya_link",
29
+ "manual": "DZPfya_manual",
29
30
  "area": "DZPfya_area",
30
- "headActions": "DZPfya_headActions",
31
+ "headText": "DZPfya_headText",
32
+ "sectionHead": "DZPfya_sectionHead",
33
+ "body": "DZPfya_body",
31
34
  "hint": "DZPfya_hint",
32
35
  "statusLine": "DZPfya_statusLine",
36
+ "choices": "DZPfya_choices",
33
37
  "check": "DZPfya_check",
34
- "manual": "DZPfya_manual",
35
38
  "desc": "DZPfya_desc",
39
+ "count": "DZPfya_count",
40
+ "sectionLabel": "DZPfya_sectionLabel",
36
41
  "fieldLabel": "DZPfya_fieldLabel",
37
- "body": "DZPfya_body",
38
- "section": "DZPfya_section",
42
+ "header": "DZPfya_header",
39
43
  "card": "DZPfya_card",
40
- "name": "DZPfya_name",
41
- "sectionHead": "DZPfya_sectionHead",
42
- "sectionLabel": "DZPfya_sectionLabel",
43
- "count": "DZPfya_count",
44
- "headText": "DZPfya_headText",
45
44
  "error": "DZPfya_error",
46
- "danger": "DZPfya_danger"
45
+ "danger": "DZPfya_danger",
46
+ "section": "DZPfya_section"
47
47
  };
48
48
  //#endregion
49
49
  //#region src/client/CodeArtsPluginCard.tsx
@@ -84,48 +84,28 @@ function formatCredits(value) {
84
84
  return Number.isFinite(value) ? new Intl.NumberFormat().format(Math.round(value)) : "0";
85
85
  }
86
86
  /**
87
+ * 领取结果 → 文案。
88
+ *
89
+ * 本地状态(claimed/already/没有这个活动)用界面自己的话;只有 failed 才透出
90
+ * 上游给的 reason —— 那句往往才是排障要的东西(签名失败、404、非 JSON…)。
91
+ */
92
+ function claimText(data, t) {
93
+ if (data?.claimed === true) return t("claimDone", { amount: formatCredits(data.amount ?? 0) });
94
+ if (data?.state === "already") return t("claimAlready");
95
+ if (data?.state === "unavailable") return t("claimUnavailable");
96
+ return t("claimFailed", { message: typeof data?.message === "string" && data.message !== "" ? data.message : "request failed" });
97
+ }
98
+ /**
87
99
  * 积分区块。
88
100
  *
89
101
  * 2026-10 从官方 CodeArts Agent 客户端挖出积分接口并实测通过(见 src/credits.ts),
90
102
  * 所以这里能直接显示账号总额度 —— 与下面的「本机用量」互补:一个是账号级、一个是
91
103
  * 本机级。拉不到时显示原因(creditsError),不静默吞掉。
92
104
  */
93
- function CreditsSection({ credits, error, t, onClaimed }) {
94
- const [claimState, setClaimState] = (0, react.useState)(() => {
95
- try {
96
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
97
- if (typeof localStorage !== "undefined" && localStorage.getItem("codearts-claim-date") === today) return "success";
98
- } catch {}
99
- return "idle";
100
- });
101
- const [claimMessage, setClaimMessage] = (0, react.useState)("");
105
+ function CreditsSection({ credits, error, t, onClaim, claiming, claimResult, justClaimed }) {
102
106
  if (credits === void 0 && error === void 0) return null;
103
- const handleClaim = async () => {
104
- setClaimState("loading");
105
- try {
106
- const res = await fetch("/api/codearts/claim", {
107
- method: "POST",
108
- headers: { Accept: "application/json" },
109
- credentials: "same-origin"
110
- });
111
- const data = await res.json().catch(() => void 0);
112
- if (res.ok && data?.ok === true) {
113
- setClaimState("success");
114
- try {
115
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
116
- if (typeof localStorage !== "undefined") localStorage.setItem("codearts-claim-date", today);
117
- } catch {}
118
- onClaimed?.();
119
- } else {
120
- setClaimState("error");
121
- setClaimMessage(data?.message ?? `HTTP ${res.status}`);
122
- }
123
- } catch (err) {
124
- setClaimState("error");
125
- setClaimMessage(err instanceof Error ? err.message : String(err));
126
- }
127
- };
128
107
  const c = credits?.credits;
108
+ const claimedToday = justClaimed || credits?.welfare?.claimedToday === true;
129
109
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
130
110
  className: CodeArtsPluginCard_module_css_default.section,
131
111
  children: [
@@ -142,12 +122,12 @@ function CreditsSection({ credits, error, t, onClaimed }) {
142
122
  remain: formatCredits(c.total.remain),
143
123
  total: formatCredits(c.total.total)
144
124
  })
145
- }), onClaimed !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
125
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
146
126
  variant: "outline",
147
127
  size: "sm",
148
- disabled: claimState === "loading" || claimState === "success",
149
- onClick: () => void handleClaim(),
150
- children: claimState === "loading" ? t("claiming") : claimState === "success" ? t("claimSuccess") : t("claimButton")
128
+ disabled: claiming || claimedToday,
129
+ onClick: onClaim,
130
+ children: claiming ? t("claimPending") : claimedToday ? t("claimButtonDone") : t("claimButton")
151
131
  })]
152
132
  })]
153
133
  }),
@@ -155,9 +135,9 @@ function CreditsSection({ credits, error, t, onClaimed }) {
155
135
  className: CodeArtsPluginCard_module_css_default.hint,
156
136
  children: t("creditsUnavailable", { message: error })
157
137
  }),
158
- claimState === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
138
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
159
139
  className: CodeArtsPluginCard_module_css_default.hint,
160
- children: t("claimFailed", { message: claimMessage })
140
+ children: claimResult ?? t("claimHint")
161
141
  }),
162
142
  c !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
163
143
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -328,6 +308,30 @@ function CodeArtsPluginCard({ t }) {
328
308
  const [modelError, setModelError] = (0, react.useState)(null);
329
309
  /** 账号身份是否展开:默认遮蔽(false = 显示 *******)。 */
330
310
  const [accountsRevealed, setAccountsRevealed] = (0, react.useState)(false);
311
+ const [claiming, setClaiming] = (0, react.useState)(false);
312
+ const [claimResult, setClaimResult] = (0, react.useState)(null);
313
+ const [justClaimed, setJustClaimed] = (0, react.useState)(false);
314
+ /**
315
+ * 领一次每日签到积分。
316
+ *
317
+ * 除了这一次刚领成,「今天已领」都来自上游的福利活动状态(积分快照里的 welfare),
318
+ * 所以刷新、重开窗口都不会把按钮说错。领完不记日期:去重在上游按天做。
319
+ */
320
+ async function claim() {
321
+ setClaiming(true);
322
+ try {
323
+ const data = await (await fetch("/api/codearts/claim", { method: "POST" })).json().catch(() => null);
324
+ setClaimResult(claimText(data, t));
325
+ if (data?.claimed === true) {
326
+ setJustClaimed(true);
327
+ await refresh();
328
+ }
329
+ } catch (error) {
330
+ setClaimResult(t("claimFailed", { message: error instanceof Error ? error.message : String(error) }));
331
+ } finally {
332
+ setClaiming(false);
333
+ }
334
+ }
331
335
  async function refresh() {
332
336
  try {
333
337
  const data = await (await fetch("/api/codearts/status", { headers: { Accept: "application/json" } })).json();
@@ -781,7 +785,10 @@ function CodeArtsPluginCard({ t }) {
781
785
  credits: status?.credits,
782
786
  error: status?.creditsError,
783
787
  t,
784
- onClaimed: () => void refresh()
788
+ onClaim: () => void claim(),
789
+ claiming,
790
+ claimResult,
791
+ justClaimed
785
792
  }),
786
793
  status?.usage !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageSection, {
787
794
  usage: status.usage,
@@ -948,9 +955,13 @@ const zh = {
948
955
  creditsUnavailable: "积分不可用:{message}",
949
956
  creditsDock: "积分 {remain}",
950
957
  creditsDockTitle: "CodeArts 剩余积分 {remain} / {total}",
951
- claimButton: "领取积分",
952
- claiming: "领取中…",
953
- claimSuccess: "已领取",
958
+ claimButton: "签到领积分",
959
+ claimButtonDone: "今日已领",
960
+ claimPending: "领取中…",
961
+ claimHint: "每天领一次 1000 积分,走官方福利中心接口(与客户端里那个「每日签到」同一件事)。",
962
+ claimDone: "已领取 {amount} 积分",
963
+ claimAlready: "今日已领,明天再来",
964
+ claimUnavailable: "这个账号当前没有可领的签到活动",
954
965
  claimFailed: "领取失败:{message}"
955
966
  };
956
967
  const en = {
@@ -1020,9 +1031,13 @@ const en = {
1020
1031
  creditsUnavailable: "Credits unavailable: {message}",
1021
1032
  creditsDock: "Credits {remain}",
1022
1033
  creditsDockTitle: "CodeArts credits left {remain} / {total}",
1023
- claimButton: "Claim credits",
1024
- claiming: "Claiming…",
1025
- claimSuccess: "Claimed",
1034
+ claimButton: "Claim daily credits",
1035
+ claimButtonDone: "Claimed today",
1036
+ claimPending: "Claiming…",
1037
+ claimHint: "Claim 1,000 credits once a day through CodeArts’ welfare centre — the same check-in the official client does.",
1038
+ claimDone: "Claimed {amount} credits",
1039
+ claimAlready: "Already claimed today — come back tomorrow",
1040
+ claimUnavailable: "No check-in campaign available for this account",
1026
1041
  claimFailed: "Claim failed: {message}"
1027
1042
  };
1028
1043
  //#endregion
package/lib/index.d.mts CHANGED
@@ -581,10 +581,24 @@ interface CodeArtsTokenQuota {
581
581
  /** 总额度。 */
582
582
  quota: number;
583
583
  }
584
+ /** 每日签到活动当前状态(来自福利中心 `/v1/ops/delivery`)。 */
585
+ interface CodeArtsDailyWelfare {
586
+ /** 上游说这条活动现在点得动吗。 */
587
+ claimable: boolean;
588
+ /**
589
+ * 「今天已经领过了」。单独判出来给卡片改按钮文案用 —— 不可领的原因有好几种
590
+ * (已领 / 要先去官网登录 / 账号没资格),只有这一种该把按钮变成「今日已领」。
591
+ */
592
+ claimedToday: boolean;
593
+ /** 领一次给多少积分。 */
594
+ amount: number;
595
+ }
584
596
  interface CodeArtsCreditSnapshot {
585
597
  credits: CodeArtsCredits;
586
598
  /** 拿不到(接口失败/不支持)时为 undefined,不影响积分本身。 */
587
599
  tokens?: CodeArtsTokenQuota;
600
+ /** 没有这个活动(或账号是 token 套餐)时为 undefined。 */
601
+ welfare?: CodeArtsDailyWelfare;
588
602
  }
589
603
  /** 缓存里的一个结果:成功给值,失败给消息 —— 失败也要能被卡片看见。 */
590
604
  type CodeArtsCreditState = {
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as codeartsAccountsPath, S as CodeArtsCredentialStore, T as startLogin, _ as CodeArtsCatalog, a as SNAP_HOST, b as isBenefitModel, c as claimBenefit, d as fetchModels, f as isConcurrencyCap, g as signedGet, h as prepareChatBody, i as refreshCatalog, l as degradeTokenCeiling, m as maxTokensCeilingFor, n as codeartsUsagePath, o as canonicalModel, p as isParamInvalidFrame, r as discoverModels, s as chatStream, t as CodeArtsUsageTracker, u as fetchCallerIdentity, v as FALLBACK_CODEARTS_MODELS, w as codeartsAuthPath, x as CodeArtsAccountStore, y as filterEnabledModels } from "./usage-3W88T_H1.mjs";
1
+ import { C as isBenefitModel, D as codeartsAuthPath, E as codeartsAccountsPath, O as startLogin, S as filterEnabledModels, T as CodeArtsCredentialStore, _ as pickDailyWelfareCampaign, a as SNAP_HOST, b as CodeArtsCatalog, c as claimDailyWelfare, d as fetchModels, f as fetchWelfareCampaigns, g as maxTokensCeilingFor, h as isWelfareClaimedStatus, i as refreshCatalog, 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 prepareChatBody, w as CodeArtsAccountStore, x as FALLBACK_CODEARTS_MODELS, y as signedGet } from "./usage-B4vQb1sP.mjs";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { randomBytes, timingSafeEqual } from "node:crypto";
4
4
  import { createServer } from "node:http";
@@ -273,18 +273,31 @@ function errorOf(action, result) {
273
273
  * 它失败只让 `tokens` 缺席 —— 别让一个次要接口把主数字一起拖没。
274
274
  */
275
275
  async function fetchCredits(credential, signal) {
276
- const [statistics, balance] = await Promise.all([signedGet(`${SNAP_HOST}${EP_STATISTICS_PLUGIN}`, credential, void 0, signal), signedGet(`${BENEFIT_GATEWAY_HOST}${EP_TOKEN_BALANCE}`, credential, void 0, signal).catch(() => ({
277
- ok: false,
278
- status: 0,
279
- message: "request failed"
280
- }))]);
276
+ const [statistics, balance, welfare] = await Promise.all([
277
+ signedGet(`${SNAP_HOST}${EP_STATISTICS_PLUGIN}`, credential, void 0, signal),
278
+ signedGet(`${BENEFIT_GATEWAY_HOST}${EP_TOKEN_BALANCE}`, credential, void 0, signal).catch(() => ({
279
+ ok: false,
280
+ status: 0,
281
+ message: "request failed"
282
+ })),
283
+ fetchWelfareCampaigns(credential, signal).then((campaigns) => {
284
+ const daily = pickDailyWelfareCampaign(campaigns);
285
+ if (daily === void 0) return void 0;
286
+ return {
287
+ claimable: daily.claimable,
288
+ claimedToday: !daily.claimable && isWelfareClaimedStatus(daily.status),
289
+ amount: daily.amount
290
+ };
291
+ }).catch(() => void 0)
292
+ ]);
281
293
  if (!statistics.ok) throw errorOf("statistics", statistics);
282
294
  const credits = parseCreditStatistics(safeJson(statistics.text));
283
295
  if (credits === void 0) throw new Error("statistics response carries no credit metrics");
284
296
  const tokens = balance.ok ? parseTokenBalance(safeJson(balance.text)) : void 0;
285
297
  return {
286
298
  credits,
287
- ...tokens === void 0 ? {} : { tokens }
299
+ ...tokens === void 0 ? {} : { tokens },
300
+ ...welfare === void 0 ? {} : { welfare }
288
301
  };
289
302
  }
290
303
  function safeJson(text) {
@@ -999,7 +1012,7 @@ const CODEARTS_LOGIN_PATH = "/api/codearts/login";
999
1012
  const CODEARTS_LOGIN_STATUS_PATH = "/api/codearts/login/status";
1000
1013
  /** 删除某个账号:DELETE /api/codearts/accounts?id=<accountId> */
1001
1014
  const CODEARTS_ACCOUNTS_PATH = "/api/codearts/accounts";
1002
- /** POST /api/codearts/claim — 手动领取限时福利(签到领积分,幂等)。 */
1015
+ /** POST /api/codearts/claim — 领一次福利中心的每日签到积分(幂等由上游按天去重)。 */
1003
1016
  const CODEARTS_CLAIM_PATH = "/api/codearts/claim";
1004
1017
  /**
1005
1018
  * 由完整目录 + 已存 enabled 计算 selection。
@@ -1333,7 +1346,7 @@ function accountsHandler(deps) {
1333
1346
  }
1334
1347
  };
1335
1348
  }
1336
- /** POST /api/codearts/claim — 手动领取限时福利(签到领积分,幂等)。 */
1349
+ /** POST /api/codearts/claim — 领一次每日签到积分;未领到也回 200(看 claimed/state)。 */
1337
1350
  function claimHandler(deps) {
1338
1351
  return async (req, res) => {
1339
1352
  if (req.method !== "POST") {
@@ -1350,14 +1363,13 @@ function claimHandler(deps) {
1350
1363
  }
1351
1364
  try {
1352
1365
  const result = await deps.claim();
1353
- if (result.ok) json(res, 200, { ok: true });
1354
- else json(res, 500, {
1355
- ok: false,
1356
- message: result.message
1357
- });
1366
+ json(res, result.ok ? 200 : 500, result);
1358
1367
  } catch (error) {
1359
1368
  json(res, 500, {
1360
1369
  ok: false,
1370
+ claimed: false,
1371
+ state: "failed",
1372
+ amount: 0,
1361
1373
  message: safeMessage(error)
1362
1374
  });
1363
1375
  }
@@ -1654,17 +1666,29 @@ function apply(ctx, rawConfig) {
1654
1666
  if (stored === void 0 || stored.stale === true) continue;
1655
1667
  const resolved = await store.resolve(id);
1656
1668
  if (resolved === void 0) continue;
1657
- await claimBenefit(resolved, AbortSignal.timeout(1e4));
1658
- credits.invalidate();
1659
- return { ok: true };
1669
+ const outcome = await claimDailyWelfare(resolved, AbortSignal.timeout(2e4));
1670
+ if (outcome.state === "claimed") credits.invalidate();
1671
+ return {
1672
+ ok: outcome.state !== "failed",
1673
+ claimed: outcome.state === "claimed",
1674
+ state: outcome.state,
1675
+ amount: outcome.amount,
1676
+ message: outcome.message
1677
+ };
1660
1678
  }
1661
1679
  return {
1662
1680
  ok: false,
1681
+ claimed: false,
1682
+ state: "failed",
1683
+ amount: 0,
1663
1684
  message: "no usable credential"
1664
1685
  };
1665
1686
  } catch (error) {
1666
1687
  return {
1667
1688
  ok: false,
1689
+ claimed: false,
1690
+ state: "failed",
1691
+ amount: 0,
1668
1692
  message: error instanceof Error ? error.message : String(error)
1669
1693
  };
1670
1694
  }
@@ -1168,13 +1168,17 @@ var upstream_exports = /* @__PURE__ */ __exportAll({
1168
1168
  canonicalModel: () => canonicalModel,
1169
1169
  chatStream: () => chatStream,
1170
1170
  claimBenefit: () => claimBenefit,
1171
+ claimDailyWelfare: () => claimDailyWelfare,
1171
1172
  degradeTokenCeiling: () => degradeTokenCeiling,
1172
1173
  estimateTokens: () => estimateTokens,
1173
1174
  fetchCallerIdentity: () => fetchCallerIdentity,
1174
1175
  fetchModels: () => fetchModels,
1176
+ fetchWelfareCampaigns: () => fetchWelfareCampaigns,
1175
1177
  isConcurrencyCap: () => isConcurrencyCap,
1176
1178
  isParamInvalidFrame: () => isParamInvalidFrame,
1179
+ isWelfareClaimedStatus: () => isWelfareClaimedStatus,
1177
1180
  maxTokensCeilingFor: () => maxTokensCeilingFor,
1181
+ pickDailyWelfareCampaign: () => pickDailyWelfareCampaign,
1178
1182
  prepareChatBody: () => prepareChatBody,
1179
1183
  signedGet: () => signedGet
1180
1184
  });
@@ -1883,9 +1887,14 @@ async function fetchModels(credential, signal) {
1883
1887
  };
1884
1888
  }
1885
1889
  /**
1886
- * 领取限时福利(幂等:已领取返回成功)。官方客户端打开模型菜单即调用。
1887
- * 不领取时福利模型调用必报 InferHub.4004.200 benefit not found。
1890
+ * 开通「限时福利」(免费模型)渠道,幂等:已开通就原样返回。官方客户端打开模型菜单
1891
+ * 即调用(GET),用户点「领取」时也调(POST)——两种方法实测都返回 200/0000。
1892
+ * 不开通时福利模型调用必报 InferHub.4004.200 benefit not found。
1888
1893
  * 需要 AK/SK 签名(凭证要带 accessKeyId/secretAccessKey)。
1894
+ *
1895
+ * **它不发积分。** 返回体只是一条渠道开通记录(channel / create_time / update_time,
1896
+ * 幂等所以时间戳是旧的),账号积分由套餐决定(见 credits.ts),领取动作在官网签到页。
1897
+ * 别把它当「领取积分」用 —— 早先卡片这么用,结果就是「点了显示已领取、积分没变」。
1889
1898
  */
1890
1899
  async function claimBenefit(credential, abort) {
1891
1900
  if (!credential.accessKeyId || !credential.secretAccessKey) return;
@@ -1908,13 +1917,188 @@ async function claimBenefit(credential, abort) {
1908
1917
  const resp = await fetch(req, { signal: abort });
1909
1918
  const raw = await resp.text().catch(() => "");
1910
1919
  if (!resp.ok) throw new Error(`claim benefit failed: ${resp.status} ${raw.slice(0, 200)}`);
1920
+ let json;
1921
+ try {
1922
+ json = JSON.parse(raw);
1923
+ } catch {
1924
+ throw new Error(`claim benefit: unexpected non-JSON response: ${raw.slice(0, 200)}`);
1925
+ }
1926
+ if (json.error_code !== "0000") throw new Error(`claim benefit rejected: ${json.error_code ?? "(no error_code)"} ${json.error_msg ?? ""}`.trim());
1927
+ }
1928
+ /**
1929
+ * 福利中心(活动积分)接口:26.9.901 起应用内「每日签到领 1000 积分」走这套。
1930
+ *
1931
+ * host 与积分统计是同一个 snap-access 网关,路径前缀 `/v1/ops`,鉴权头对齐官方客户端
1932
+ * 的 `Pz()`(`x-auth-token` + `Agent-Type: PromptCenter`),再补上我们直连必须的
1933
+ * AK/SK 签名。三个实测出来的坑:
1934
+ *
1935
+ * 1. bundle 里 URL 有两套变体,带前缀的 `/PromptCenterService/v1/ops/*` 在这个网关上
1936
+ * 回 **404 APIG.0101(未发布)** —— 只有裸 `/v1/ops/*` 能用;
1937
+ * 2. 只带 `x-auth-token` 会被拒(`APIG.0301 decrypt token fail`);
1938
+ * 3. **业务失败也回 HTTP 200**,成败只看 body 的 `code`(0 才是成功)。
1939
+ * 今天已领时是 `code:40001 尚未到达权益刷新时间,暂时无法领取`。
1940
+ *
1941
+ * 领的是「活动」而不是额度:`/ops/delivery` 给活动清单(含 campaignId / status /
1942
+ * claimable),`/ops/claim` 按 campaignId 领。`idempotentKey` 上游不返回,官方客户端
1943
+ * 自己生成随机串(与 `x-snap-traceid` 同一个生成器),按天去重落在服务端。
1944
+ */
1945
+ const EP_OPS_DELIVERY = "/v1/ops/delivery";
1946
+ const EP_OPS_CLAIM = "/v1/ops/claim";
1947
+ /** 每日签到活动的 type(实测为 campaignId 1,benefitAmount 1000 CREDIT)。 */
1948
+ const WELFARE_DAILY_TYPE = "USER_LOGIN";
1949
+ /** 上游按钮文案的状态机:这几个都表示「今天已经领过了」。 */
1950
+ const WELFARE_ALREADY_STATES = /* @__PURE__ */ new Set([
1951
+ "CLAIMED",
1952
+ "CONFIRMED",
1953
+ "CONSUMED"
1954
+ ]);
1955
+ /** 活动状态是不是「今天已经领过了」(区别于「要先登录」「没资格」这些同样不可领的情况)。 */
1956
+ function isWelfareClaimedStatus(status) {
1957
+ return WELFARE_ALREADY_STATES.has(status);
1958
+ }
1959
+ /**
1960
+ * 「今日已领」的业务码。
1961
+ *
1962
+ * 这个分支不是冗余的:领取成功后的几十秒里 delivery 有可能还回一份**陈旧**的
1963
+ * `claimable:true / ELIGIBLE`(实测 2026-10-16,领完立刻读会看到),于是会真的
1964
+ * POST 第二次 —— 服务端按天去重回这个码,所以按「已领」处理,不能当失败。
1965
+ */
1966
+ const WELFARE_CODE_NOT_YET = 40001;
1967
+ /** 福利接口的请求:签名 + 官方那套头,返回解析后的 body。 */
1968
+ async function welfareRequest(url, method, credential, body, signal) {
1969
+ const req = new Request(url, {
1970
+ method,
1971
+ headers: {
1972
+ "Content-Type": "application/json",
1973
+ "X-Language": "zh-cn",
1974
+ "x-snap-traceid": hex32(),
1975
+ "x-auth-token": credential.token,
1976
+ "Agent-Type": "PromptCenter",
1977
+ "X-Security-Token": credential.securityToken
1978
+ },
1979
+ ...body === void 0 ? {} : { body }
1980
+ });
1981
+ const bodyBuf = Buffer.from(body ?? "");
1982
+ if (credential.accessKeyId !== void 0 && credential.secretAccessKey !== void 0) signRequest(req, bodyBuf, {
1983
+ accessKeyId: credential.accessKeyId,
1984
+ secretAccessKey: credential.secretAccessKey,
1985
+ securityToken: credential.securityToken
1986
+ });
1987
+ let resp;
1988
+ try {
1989
+ resp = await fetch(req, { signal });
1990
+ } catch (error) {
1991
+ return {
1992
+ ok: false,
1993
+ message: error instanceof Error ? error.message : String(error)
1994
+ };
1995
+ }
1996
+ const text = await resp.text().catch(() => "");
1997
+ if (!resp.ok) return {
1998
+ ok: false,
1999
+ message: `HTTP ${resp.status} ${text.slice(0, 200)}`
2000
+ };
2001
+ let parsed;
1911
2002
  try {
1912
- const json = JSON.parse(raw);
1913
- if (json.error_code && json.error_code !== "0000") throw new Error(`claim benefit rejected: ${json.error_code} ${json.error_msg ?? ""}`);
1914
- } catch (e) {
1915
- if (e instanceof SyntaxError) return;
1916
- throw e;
2003
+ parsed = JSON.parse(text);
2004
+ } catch {
2005
+ return {
2006
+ ok: false,
2007
+ message: `unexpected non-JSON response: ${text.slice(0, 200)}`
2008
+ };
1917
2009
  }
2010
+ const envelope = asRecord(parsed);
2011
+ return {
2012
+ ok: true,
2013
+ code: typeof envelope.code === "number" ? envelope.code : -1,
2014
+ message: firstString(envelope.message),
2015
+ data: envelope.data
2016
+ };
2017
+ }
2018
+ /** 活动清单(`GET /v1/ops/delivery?channel=IDE`)。 */
2019
+ async function fetchWelfareCampaigns(credential, signal) {
2020
+ const resp = await welfareRequest(`${SNAP_HOST}${EP_OPS_DELIVERY}?channel=IDE`, "GET", credential, void 0, signal);
2021
+ if (!resp.ok) throw new Error(`welfare delivery: ${resp.message}`);
2022
+ return asArray(asRecord(resp.data).items).flatMap((item) => {
2023
+ const raw = asRecord(item);
2024
+ const campaignId = typeof raw.campaignId === "number" ? raw.campaignId : NaN;
2025
+ if (Number.isFinite(campaignId) === false) return [];
2026
+ return [{
2027
+ campaignId,
2028
+ title: firstString(raw.title),
2029
+ type: firstString(raw.type),
2030
+ amount: typeof raw.benefitAmount === "number" ? raw.benefitAmount : 0,
2031
+ unit: firstString(raw.benefitUnit),
2032
+ claimable: raw.claimable === true,
2033
+ status: firstString(raw.status)
2034
+ }];
2035
+ });
2036
+ }
2037
+ /**
2038
+ * 从活动清单里挑「每日签到」那条。
2039
+ *
2040
+ * 同一个 type 理论上可能有多条(上游换活动的时候),优先挑当前可领的那条。
2041
+ */
2042
+ function pickDailyWelfareCampaign(campaigns) {
2043
+ const daily = campaigns.filter((c) => c.type === WELFARE_DAILY_TYPE && c.unit === "CREDIT");
2044
+ return daily.find((c) => c.claimable) ?? daily[0];
2045
+ }
2046
+ /**
2047
+ * 领一次每日签到积分。
2048
+ *
2049
+ * 不抛异常:这是卡片上一个按钮的后续,失败要变成能显示的文案而不是断掉的请求。
2050
+ */
2051
+ async function claimDailyWelfare(credential, signal) {
2052
+ if (credential.accessKeyId === void 0 || credential.secretAccessKey === void 0) return {
2053
+ state: "failed",
2054
+ amount: 0,
2055
+ message: "no AK/SK to sign"
2056
+ };
2057
+ let campaigns;
2058
+ try {
2059
+ campaigns = await fetchWelfareCampaigns(credential, signal);
2060
+ } catch (error) {
2061
+ return {
2062
+ state: "failed",
2063
+ amount: 0,
2064
+ message: error instanceof Error ? error.message : String(error)
2065
+ };
2066
+ }
2067
+ const target = pickDailyWelfareCampaign(campaigns);
2068
+ if (target === void 0) return {
2069
+ state: "unavailable",
2070
+ amount: 0,
2071
+ message: "no daily check-in campaign"
2072
+ };
2073
+ if (!target.claimable) return isWelfareClaimedStatus(target.status) ? {
2074
+ state: "already",
2075
+ amount: 0,
2076
+ message: ""
2077
+ } : {
2078
+ state: "unavailable",
2079
+ amount: 0,
2080
+ message: target.status
2081
+ };
2082
+ const resp = await welfareRequest(`${SNAP_HOST}${EP_OPS_CLAIM}`, "POST", credential, JSON.stringify({
2083
+ campaignId: target.campaignId,
2084
+ idempotentKey: hex32(),
2085
+ channel: "IDE"
2086
+ }), signal);
2087
+ if (!resp.ok) return {
2088
+ state: "failed",
2089
+ amount: 0,
2090
+ message: resp.message
2091
+ };
2092
+ if (resp.code === 0) return {
2093
+ state: "claimed",
2094
+ amount: target.amount,
2095
+ message: resp.message
2096
+ };
2097
+ return {
2098
+ state: resp.code === WELFARE_CODE_NOT_YET ? "already" : "failed",
2099
+ amount: 0,
2100
+ message: resp.message
2101
+ };
1918
2102
  }
1919
2103
  //#endregion
1920
2104
  //#region src/discovery.ts
@@ -2220,4 +2404,4 @@ function sumOf(store) {
2220
2404
  return out;
2221
2405
  }
2222
2406
  //#endregion
2223
- export { codeartsAccountsPath as C, CodeArtsCredentialStore as S, startLogin as T, CodeArtsCatalog as _, SNAP_HOST as a, isBenefitModel as b, claimBenefit as c, fetchModels as d, isConcurrencyCap as f, signedGet as g, prepareChatBody as h, refreshCatalog as i, degradeTokenCeiling as l, maxTokensCeilingFor as m, codeartsUsagePath as n, canonicalModel as o, isParamInvalidFrame as p, discoverModels as r, chatStream as s, CodeArtsUsageTracker as t, fetchCallerIdentity as u, FALLBACK_CODEARTS_MODELS as v, codeartsAuthPath as w, CodeArtsAccountStore as x, filterEnabledModels as y };
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 };
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.4.0",
5
+ "version": "0.4.2",
6
6
  "author": "elegant01",
7
7
  "keywords": [
8
8
  "dsh-plugin",