dsh-codearts 1.1.4 → 1.2.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
@@ -1572,10 +1572,14 @@ declare const name = "llm-codearts";
1572
1572
  declare const inject: string[];
1573
1573
  /** Settings namespace owning the configuration card. */
1574
1574
  declare const CODEARTS_SETTINGS_NS: SettingsNamespace;
1575
- /** Plugin configuration (MVP: just the model allowlist). */
1575
+ /** Plugin configuration. */
1576
1576
  interface Config {
1577
1577
  /** Allowlist of CodeArts model ids offered in the model pickers. */
1578
1578
  enabledModels?: string[];
1579
+ /** 对话输入框下方那行「积分 N」是否显示。缺省 = 显示(老配置不改行为)。 */
1580
+ showCreditDock?: boolean;
1581
+ /** 等上游开流时那行「排队第 N 位」是否显示。缺省 = 显示。 */
1582
+ showQueueDock?: boolean;
1579
1583
  }
1580
1584
  declare const Config: z<Config>;
1581
1585
  declare function apply(ctx: Context, rawConfig: Config): void;
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { A as filterEnabledModels, C as rateLimitFrame, D as FALLBACK_CODEARTS_MODELS, E as CodeArtsCatalog, F as MANUAL_ACCOUNT_ID, I as codeartsAccountsPath, L as codeartsAuthPath, M as isBenefitModel, N as CodeArtsAccountStore, O as clearBenefitBlocked, P as CodeArtsCredentialStore, R as startLogin, S as prepareChatBody, T as upstreamErrorMessage, _ as isQueueableRate, a as SNAP_HOST, b as maxTokensCeilingFor, c as claimDailyWelfare, d as fetchCallerIdentity, f as fetchModels, g as isParamInvalidFrame, h as isConcurrencyCap, i as refreshCatalog, j as isBenefitBlocked, k as creditRatioLabel, 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 isTpmLimit, w as signedGet, x as pickDailyWelfareCampaign, y as isWelfareClaimedStatus } from "./usage-B0NfG8vZ.mjs";
1
+ import { A as filterEnabledModels, C as rateLimitFrame, D as FALLBACK_CODEARTS_MODELS, E as CodeArtsCatalog, F as MANUAL_ACCOUNT_ID, I as codeartsAccountsPath, L as codeartsAuthPath, M as isBenefitModel, N as CodeArtsAccountStore, O as clearBenefitBlocked, P as CodeArtsCredentialStore, R as startLogin, S as prepareChatBody, T as upstreamErrorMessage, _ as isQueueableRate, a as SNAP_HOST, b as maxTokensCeilingFor, c as claimDailyWelfare, d as fetchCallerIdentity, f as fetchModels, g as isParamInvalidFrame, h as isConcurrencyCap, i as refreshCatalog, j as isBenefitBlocked, k as creditRatioLabel, 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 isTpmLimit, w as signedGet, x as pickDailyWelfareCampaign, y as isWelfareClaimedStatus } from "./usage-05PgtUIP.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";
@@ -1404,6 +1404,11 @@ const CODEARTS_CLAIM_PATH = "/api/codearts/claim";
1404
1404
  const CODEARTS_REFRESH_MODELS_PATH = "/api/codearts/refresh-models";
1405
1405
  /** GET /api/codearts/queue — 上游排队位置(只在等开流期间有值,底部积分条旁边展示)。 */
1406
1406
  const CODEARTS_QUEUE_PATH = "/api/codearts/queue";
1407
+ /**
1408
+ * GET/POST /api/codearts/dock — 对话底部那一行的显隐开关。
1409
+ * GET 给当前值与 writable,POST 收 `{credits?, queue?}` 写进 settings。
1410
+ */
1411
+ const CODEARTS_DOCK_PATH = "/api/codearts/dock";
1407
1412
  /** 最近出站请求的**形状**摘要(只有 role 名与计数,不含内容),排障用。 */
1408
1413
  const CODEARTS_DEBUG_PATH = "/api/codearts/debug";
1409
1414
  /**
@@ -1422,6 +1427,7 @@ function selectionOf(models, enabled, writable) {
1422
1427
  benefit: m.benefit,
1423
1428
  blocked: m.blocked,
1424
1429
  ratio: m.ratio,
1430
+ supportsImages: m.supportsImages,
1425
1431
  enabled: restricted ? allowed.has(m.id) : true
1426
1432
  })),
1427
1433
  restricted,
@@ -1438,10 +1444,32 @@ function choicesOf(deps) {
1438
1444
  name: m.name,
1439
1445
  benefit,
1440
1446
  blocked: isBenefitBlocked(m.id),
1447
+ ...ratio === void 0 ? {} : { ratio },
1448
+ ...m.supportsImages === true ? { supportsImages: true } : {}
1449
+ };
1450
+ });
1451
+ }
1452
+ /**
1453
+ * 目录 -> 底栏/卡片用的模型徽章。
1454
+ *
1455
+ * 倍率在这里就算好(`creditRatioLabel`:福利恒为 `x0.00`),客户端拿到的是可以直接
1456
+ * 印出来的字符串 —— 底栏那一行要把它作为独立的一段,所以不能只给带尾巴的名字。
1457
+ */
1458
+ function modelBadgesOf(models) {
1459
+ return models.map((m) => {
1460
+ const ratio = creditRatioLabel(m.ratio, isBenefitModel(m.id));
1461
+ return {
1462
+ id: m.id,
1463
+ name: m.name,
1441
1464
  ...ratio === void 0 ? {} : { ratio }
1442
1465
  };
1443
1466
  });
1444
1467
  }
1468
+ /** 开关的默认值:没有 settings provider(或老配置没这两个键)时保持原有行为 —— 都显示。 */
1469
+ const DEFAULT_DOCK_FLAGS = {
1470
+ credits: true,
1471
+ queue: true
1472
+ };
1445
1473
  /**
1446
1474
  * 把逐账号的领取结果汇总成一条:HTTP 层只看 ok,卡片看 results 逐号展示。
1447
1475
  * 有一个号失败就算整批 ok=false,但别的号领到的结果照样在 results 里。
@@ -1563,11 +1591,15 @@ function usageOf(deps) {
1563
1591
  *
1564
1592
  * 积分是「锦上添花」:拉不到也绝不能把状态接口本身拖垮,所以失败一律降级成
1565
1593
  * `creditsError` 交给卡片显示原因(超时由注入方在自己的闭包里保证)。
1594
+ *
1595
+ * `force` 是给对话底部弹层那个「刷新积分」按钮用的:不强制的话它会命中 60 秒缓存,
1596
+ * 按钮点了数字不动 —— 那还不如不给这个按钮。
1566
1597
  */
1567
- async function creditsOf(deps) {
1568
- if (deps.credits === void 0) return {};
1598
+ async function creditsOf(deps, force) {
1599
+ const load = force ? deps.refreshCredits ?? deps.credits : deps.credits;
1600
+ if (load === void 0) return {};
1569
1601
  try {
1570
- const entries = await deps.credits();
1602
+ const entries = await load();
1571
1603
  const primary = entries.find((entry) => entry.credits !== void 0) ?? entries[0];
1572
1604
  return {
1573
1605
  ...primary?.credits === void 0 ? {} : { credits: primary.credits },
@@ -1578,14 +1610,12 @@ async function creditsOf(deps) {
1578
1610
  return { creditsError: safeMessage(error) };
1579
1611
  }
1580
1612
  }
1581
- async function buildStatus(deps) {
1582
- const models = deps.models().map((m) => ({
1583
- id: m.id,
1584
- name: m.name
1585
- }));
1613
+ async function buildStatus(deps, force = false) {
1614
+ const models = modelBadgesOf(deps.models());
1586
1615
  const accounts = await deps.store.list();
1587
1616
  const cred = await deps.store.current();
1588
1617
  const usage = usageOf(deps);
1618
+ const dock = deps.dock === void 0 ? {} : { dock: deps.dock() };
1589
1619
  const piAi = await probePiAiCompat();
1590
1620
  const compat = piAi === void 0 ? {} : { piAiError: piAi };
1591
1621
  if (accounts.length === 0 || cred === void 0) return {
@@ -1593,6 +1623,7 @@ async function buildStatus(deps) {
1593
1623
  models,
1594
1624
  accounts,
1595
1625
  usage,
1626
+ ...dock,
1596
1627
  ...compat
1597
1628
  };
1598
1629
  const usable = accounts.some(accountUsable);
@@ -1601,7 +1632,8 @@ async function buildStatus(deps) {
1601
1632
  ...cred.storedAt === void 0 ? {} : { storedAt: cred.storedAt },
1602
1633
  models,
1603
1634
  accounts,
1604
- usage
1635
+ usage,
1636
+ ...dock
1605
1637
  };
1606
1638
  if (!usable) return {
1607
1639
  status: "expired",
@@ -1612,7 +1644,7 @@ async function buildStatus(deps) {
1612
1644
  status: "signed-in",
1613
1645
  ...identity,
1614
1646
  ...compat,
1615
- ...await creditsOf(deps)
1647
+ ...await creditsOf(deps, force)
1616
1648
  };
1617
1649
  }
1618
1650
  /** GET /api/codearts/status */
@@ -1627,12 +1659,65 @@ function statusHandler(deps) {
1627
1659
  return;
1628
1660
  }
1629
1661
  try {
1630
- json(res, 200, await buildStatus(deps));
1662
+ json(res, 200, await buildStatus(deps, new URL(req.url ?? "", "http://localhost").searchParams.get("force") === "1"));
1631
1663
  } catch (error) {
1632
1664
  json(res, 500, { error: safeMessage(error) });
1633
1665
  }
1634
1666
  };
1635
1667
  }
1668
+ /**
1669
+ * GET/POST /api/codearts/dock — 对话底部那一行的显隐开关读写。
1670
+ *
1671
+ * 和 enabled-models 同一套规矩:GET 给当前值 + writable,POST 只收布尔、
1672
+ * 没接 settings provider 时回 403 让卡片把勾选框置灰。
1673
+ */
1674
+ function dockHandler(deps) {
1675
+ return async (req, res) => {
1676
+ if (!loopbackRequest(req)) {
1677
+ json(res, 403, { error: "request-not-trusted" });
1678
+ return;
1679
+ }
1680
+ const current = deps.dock?.() ?? DEFAULT_DOCK_FLAGS;
1681
+ if (req.method === "GET") {
1682
+ json(res, 200, {
1683
+ dock: current,
1684
+ writable: deps.setDock !== void 0
1685
+ });
1686
+ return;
1687
+ }
1688
+ if (req.method === "POST") {
1689
+ if (deps.setDock === void 0) {
1690
+ json(res, 403, { error: "dock-not-writable" });
1691
+ return;
1692
+ }
1693
+ const type = req.headers["content-type"];
1694
+ if (typeof type !== "string" || !type.trim().toLowerCase().startsWith("application/json")) {
1695
+ json(res, 415, { error: "content-type must be application/json" });
1696
+ return;
1697
+ }
1698
+ try {
1699
+ const body = JSON.parse(await readBody(req));
1700
+ const touched = ["credits", "queue"].filter((key) => body[key] !== void 0);
1701
+ if (touched.length === 0 || touched.some((key) => typeof body[key] !== "boolean")) {
1702
+ json(res, 400, { error: "expected {\"credits\"?: boolean, \"queue\"?: boolean}" });
1703
+ return;
1704
+ }
1705
+ if (!await deps.setDock(Object.fromEntries(touched.map((key) => [key, body[key]])))) {
1706
+ json(res, 403, { error: "dock-not-writable" });
1707
+ return;
1708
+ }
1709
+ json(res, 200, {
1710
+ dock: deps.dock?.() ?? DEFAULT_DOCK_FLAGS,
1711
+ writable: true
1712
+ });
1713
+ } catch (error) {
1714
+ json(res, 500, { error: safeMessage(error) });
1715
+ }
1716
+ return;
1717
+ }
1718
+ json(res, 405, { error: "method not allowed" });
1719
+ };
1720
+ }
1636
1721
  /** GET /api/codearts/models — currently offered model ids (honours allowlist). */
1637
1722
  function modelsHandler(deps) {
1638
1723
  return async (req, res) => {
@@ -1644,10 +1729,7 @@ function modelsHandler(deps) {
1644
1729
  json(res, 403, { error: "request-not-trusted" });
1645
1730
  return;
1646
1731
  }
1647
- json(res, 200, { models: filterEnabledModels(deps.models(), deps.enabledModels?.()).map((m) => ({
1648
- id: m.id,
1649
- name: m.name
1650
- })) });
1732
+ json(res, 200, { models: modelBadgesOf(filterEnabledModels(deps.models(), deps.enabledModels?.())) });
1651
1733
  };
1652
1734
  }
1653
1735
  /**
@@ -1935,6 +2017,11 @@ function registerCodeArtsStatusRoute(ctx, deps) {
1935
2017
  path: CODEARTS_QUEUE_PATH,
1936
2018
  handler: queueHandler(deps)
1937
2019
  });
2020
+ const disposeDock = ctx.webServer.register({
2021
+ kind: "exact",
2022
+ path: CODEARTS_DOCK_PATH,
2023
+ handler: dockHandler(deps)
2024
+ });
1938
2025
  const disposeDebug = ctx.webServer.register({
1939
2026
  kind: "exact",
1940
2027
  path: CODEARTS_DEBUG_PATH,
@@ -1951,6 +2038,7 @@ function registerCodeArtsStatusRoute(ctx, deps) {
1951
2038
  disposeClaim();
1952
2039
  disposeRefresh();
1953
2040
  disposeQueue();
2041
+ disposeDock();
1954
2042
  disposeDebug();
1955
2043
  for (const s of activeLogins.values()) s.close();
1956
2044
  activeLogins.clear();
@@ -2115,7 +2203,11 @@ function unwrapConfig(value) {
2115
2203
  for (const [key, field] of Object.entries(value)) out[key] = unwrapVolatileValue(field);
2116
2204
  return out;
2117
2205
  }
2118
- const Config = z.object({ enabledModels: z.array(z.string()).description("Model ids offered in the model pickers (empty means every model)").volatile() });
2206
+ const Config = z.object({
2207
+ enabledModels: z.array(z.string()).description("Model ids offered in the model pickers (empty means every model)").volatile(),
2208
+ showCreditDock: z.boolean().default(true).description("Show the remaining-credits line under the chat input box").volatile(),
2209
+ showQueueDock: z.boolean().default(true).description("Show the upstream queue position under the chat input box").volatile()
2210
+ });
2119
2211
  function settingsCompatNamespace(ctx) {
2120
2212
  return resolveSettingsNamespaceCompat(ctx, ctx.get("settings"), CODEARTS_SETTINGS_NS);
2121
2213
  }
@@ -2187,6 +2279,19 @@ function apply(ctx, rawConfig) {
2187
2279
  await settings.update(settingsCompatNamespace(ctx), { enabledModels: [...ids] });
2188
2280
  return true;
2189
2281
  };
2282
+ const dock = () => ({
2283
+ credits: current().showCreditDock !== false,
2284
+ queue: current().showQueueDock !== false
2285
+ });
2286
+ const setDock = async (patch) => {
2287
+ const settings = ctx.get("settings");
2288
+ if (settings === void 0) return false;
2289
+ await settings.update(settingsCompatNamespace(ctx), {
2290
+ ...patch.credits === void 0 ? {} : { showCreditDock: patch.credits },
2291
+ ...patch.queue === void 0 ? {} : { showQueueDock: patch.queue }
2292
+ });
2293
+ return true;
2294
+ };
2190
2295
  /** 手动「刷新模型列表」的预算:三路来源各打一次,比每小时的后台刷新更该给足时间。 */
2191
2296
  const MANUAL_REFRESH_MS = 6e4;
2192
2297
  ctx.inject(["webServer"], (webCtx) => registerCodeArtsStatusRoute(webCtx, {
@@ -2200,6 +2305,12 @@ function apply(ctx, rawConfig) {
2200
2305
  queue: () => queue.snapshot(),
2201
2306
  recentRequests: () => recentRequestShapes(),
2202
2307
  credits: (signal) => loadCredits(signal),
2308
+ refreshCredits: (signal) => {
2309
+ credits.invalidate();
2310
+ return loadCredits(signal);
2311
+ },
2312
+ dock,
2313
+ setDock,
2203
2314
  claim: async (accountId) => {
2204
2315
  const ids = accountId === void 0 ? await store.ids() : [accountId];
2205
2316
  if (ids.length === 0) return {
@@ -2281,6 +2392,7 @@ function apply(ctx, rawConfig) {
2281
2392
  shim.close().catch(() => {});
2282
2393
  });
2283
2394
  const CATALOG_REFRESH_MS = 36e5;
2395
+ const CATALOG_EMPTY_RETRY_MS = 6e4;
2284
2396
  const abort = new AbortController();
2285
2397
  const discover = async () => {
2286
2398
  if (stopped) return;
@@ -2295,13 +2407,18 @@ function apply(ctx, rawConfig) {
2295
2407
  ctx.logger.debug("dsh-codearts: model discovery failed; keeping the current catalog", error);
2296
2408
  }
2297
2409
  };
2298
- ctx.effect(() => {
2299
- const timer = setInterval(() => void discover(), CATALOG_REFRESH_MS);
2300
- timer.unref?.();
2301
- return () => {
2302
- abort.abort();
2303
- clearInterval(timer);
2304
- };
2410
+ let discoveryTimer;
2411
+ /** 自我重排的发现循环:目录为空时加快到每分钟,有货后回到每小时。 */
2412
+ const discoverLoop = async () => {
2413
+ await discover();
2414
+ if (stopped) return;
2415
+ const delay = catalog.current().length === 0 ? CATALOG_EMPTY_RETRY_MS : CATALOG_REFRESH_MS;
2416
+ discoveryTimer = setTimeout(() => void discoverLoop(), delay);
2417
+ discoveryTimer.unref?.();
2418
+ };
2419
+ ctx.effect(() => () => {
2420
+ abort.abort();
2421
+ if (discoveryTimer !== void 0) clearTimeout(discoveryTimer);
2305
2422
  });
2306
2423
  const shim = createCodeArtsShim({
2307
2424
  store,
@@ -2314,7 +2431,7 @@ function apply(ctx, rawConfig) {
2314
2431
  usage.load().catch(() => {});
2315
2432
  shim.ready.then(async () => {
2316
2433
  if (stopped) return;
2317
- discover();
2434
+ discoverLoop();
2318
2435
  const incompatible = await probePiAiCompat();
2319
2436
  if (incompatible !== void 0) ctx.logger.error(`dsh-codearts: ${incompatible}`);
2320
2437
  const codearts = createCodeArtsAdapter({
@@ -905,6 +905,13 @@ const FALLBACK_CODEARTS_MODELS = [
905
905
  name: "DeepSeek-V4.1-Flash",
906
906
  contextWindow: 131072,
907
907
  maxTokens: 65536,
908
+ supportsImages: false
909
+ },
910
+ {
911
+ id: "deepseek-v4.1-flash-1017",
912
+ name: "DeepSeek-V4.1-Flash-1017",
913
+ contextWindow: 1048576,
914
+ maxTokens: 65536,
908
915
  supportsImages: true
909
916
  },
910
917
  {
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.1.4",
5
+ "version": "1.2.1",
6
6
  "packageManager": "pnpm@11.24.0",
7
7
  "author": "elegant01",
8
8
  "keywords": [