dsh-llm-codebuddy-power 1.2.0 → 1.3.0

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.js CHANGED
@@ -87,6 +87,13 @@ const LOGIN_POLL_INTERVAL_MS = 1e3;
87
87
  const LOGIN_RESULT_RETENTION_MS = 60 * 1e3;
88
88
  /** 含义为“浏览器登录尚未完成”的服务码。 */
89
89
  const AUTH_PENDING_CODE = 11217;
90
+ /**
91
+ * 一次性赠送活动所用的子产品编码。
92
+ *
93
+ * "裂变包"与"拉新权益包"两类赠送活动各有自己的 `PackageCode`,却共用这一个子产品
94
+ * 编码,而可续期主套餐用的是另一个,因此它是区分赠送与主体的稳定依据。
95
+ */
96
+ const CODEBUDDY_GIFT_SUB_PRODUCT_CODE = "sp_tcaca_codebuddyide_bonus_pack";
90
97
  //#endregion
91
98
  //#region lib/types/codebuddy.js
92
99
  /**
@@ -533,16 +540,19 @@ function normalizeResetTime(raw) {
533
540
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
534
541
  }
535
542
  /**
536
- * 判断某个套餐显示名是否表示一次性赠送额度。
543
+ * 判断某个计量窗口是否是一次性赠送额度。
537
544
  *
538
- * CodeBuddy 的个人计量面把推荐/裂变额度命名为“裂变包”,未来任何促销都可能
539
- * 带有“赠送”;其他一切(试用/可续期主套餐、企业行)都视为主池。显示名是
540
- * 计量面暴露的唯一区分依据,因此两种写法都会被匹配。
541
- * @param name - 窗口的显示名。
542
- * @returns 赠送套餐名返回 true。
545
+ * 依据是子产品编码:{@link CODEBUDDY_GIFT_SUB_PRODUCT_CODE} 由全部赠送活动共用,
546
+ * 而可续期主套餐用另一个编码。套餐显示名不能独立作依据 —— 两类赠送活动的
547
+ * `PackageCode` 不同、显示名也不同,只有一类名字里带“裂变”,另一类叫“拉新权益包”,
548
+ * 按名匹配会把后者错算进主池。显示名与子产品名因此只作兜底,供未来换了子产品编码
549
+ * 却仍在名字里沿用“裂变/赠送”字样的赠送活动使用。
550
+ * @param resource - `Accounts` 数组里的一项。
551
+ * @returns 赠送窗口返回 true。
543
552
  */
544
- function isGiftName(name) {
545
- return /裂变|赠送/.test(name);
553
+ function isGiftResource(resource) {
554
+ if (string(resource, "SubProductCode") === "sp_tcaca_codebuddyide_bonus_pack") return true;
555
+ return /裂变|赠送/.test(string(resource, "PackageName") ?? "") || /裂变|赠送/.test(string(resource, "SubProductName") ?? "");
546
556
  }
547
557
  /**
548
558
  * 把有可用容量的窗口聚合成一个池(used/limit 求和)。
@@ -571,13 +581,13 @@ function liveGiftWindows(windows) {
571
581
  /**
572
582
  * 构建个人快照的主池/赠送池划分,以及提示框的赠送明细(最早过期的有效赠送
573
583
  * 在前,最多 5 个)。
574
- * @param windows - 已解析的窗口。
584
+ * @param windows - 已解析的窗口,各自带 {@link UsageWindow.gift} 归属。
575
585
  * @returns 该划分,仅当对应池有可用 limit 时才包含 `main`/`gift`。
576
586
  */
577
587
  function splitPools(windows) {
578
588
  const mainWindows = [];
579
589
  const giftWindows = [];
580
- for (const window of windows) if (isGiftName(window.name)) giftWindows.push(window);
590
+ for (const window of windows) if (window.gift) giftWindows.push(window);
581
591
  else mainWindows.push(window);
582
592
  const main = poolOf(mainWindows);
583
593
  const live = liveGiftWindows(giftWindows);
@@ -616,14 +626,17 @@ function personalUsage(accounts) {
616
626
  const name = string(resource, "PackageName") ?? string(resource, "PackageCode") ?? string(resource, "ResourceId") ?? `resource_${index}`;
617
627
  const rawReset = string(resource, "CycleEndTime");
618
628
  const resetsAt = rawReset === void 0 ? void 0 : normalizeResetTime(rawReset);
629
+ const gift = isGiftResource(resource);
619
630
  if (limit <= 0) return {
620
631
  name,
632
+ gift,
621
633
  ...resetsAt === void 0 ? {} : { resetsAt }
622
634
  };
623
635
  return {
624
636
  name,
625
637
  used,
626
638
  limit,
639
+ gift,
627
640
  ...resetsAt === void 0 ? {} : { resetsAt }
628
641
  };
629
642
  });
@@ -653,6 +666,7 @@ function enterpriseUsage(data) {
653
666
  name: "enterprise",
654
667
  used,
655
668
  limit,
669
+ gift: false,
656
670
  ...reset === void 0 ? {} : { resetsAt: reset }
657
671
  };
658
672
  const main = poolOf([window]);
@@ -916,10 +930,14 @@ function isAccountRef(value) {
916
930
  /**
917
931
  * 文件读出的记录是否合法。
918
932
  *
919
- * `accounts` 与 `credit` 同为必填:一条记录说不出这一轮用的是哪个账号,悬停提示就会
920
- * 缺一行,那与"没有这条记录"没有区别,因此不完整即整条丢弃。引用还必须能在同一份
921
- * 文件的账号表里解析到 —— 一个悬空引用渲染不出账号,与缺字段是同一种残缺。
922
- * 插件尚未发布,磁盘上不存在按旧格式写下的文件,故不保留渐进读取。
933
+ * `credit` 与 `accounts` 同为必填:一条记录说不出这一轮扣了多少、用的是哪个账号,
934
+ * 就答不了它存在的唯一问题,与"没有这条记录"没有区别,因此不完整即整条丢弃。引用
935
+ * 还必须能在同一份文件的账号表里解析到 —— 一个悬空引用渲染不出账号,与缺字段是同
936
+ * 一种残缺。
937
+ *
938
+ * `requestId` 则允许缺失:它是后加的字段,已发布的旧版本写下的记录没有它。那些轮次
939
+ * 照常显示积分,只是拿不出请求 id,因此不值得为它丢掉整轮积分。存在时必须是字符串,
940
+ * 否则是损坏内容而不是旧格式。
923
941
  * @param value - 文件里的一条轮次记录。
924
942
  * @param table - 同一份文件的账号表。
925
943
  * @returns 该记录是否可用。
@@ -927,7 +945,7 @@ function isAccountRef(value) {
927
945
  function isCreditRecord(value, table) {
928
946
  if (typeof value !== "object" || value === null) return false;
929
947
  const record = value;
930
- return typeof record.credit === "number" && Number.isFinite(record.credit) && Array.isArray(record.accounts) && record.accounts.length > 0 && record.accounts.every((key) => typeof key === "string" && isAccountRef(table[key]));
948
+ return typeof record.credit === "number" && Number.isFinite(record.credit) && Array.isArray(record.accounts) && record.accounts.length > 0 && record.accounts.every((key) => typeof key === "string" && isAccountRef(table[key])) && (record.requestId === void 0 || typeof record.requestId === "string");
931
949
  }
932
950
  /**
933
951
  * 读取一个会话的信息。
@@ -1088,9 +1106,8 @@ function trimEmptyAccountFields(account) {
1088
1106
  * 把解析出的 JSON 归一化为账号集合。
1089
1107
  *
1090
1108
  * 同时接受当前形态与仅有一个账号的旧形态(顶层 `auth` + `account`):旧文件
1091
- * 迁移成单元素列表,并让那个账号成为默认账号,于是升级不会改变用户下一次
1092
- * 请求用的是谁。`defaultUid` 与 `lastUid` 指向已不存在的账号时丢弃——它们只是
1093
- * 指针,留着会让“有没有默认账号”与实际情况相反。
1109
+ * 迁移成单元素列表,于是升级不会改变用户下一次请求用的是谁。`lastUid` 指向已不
1110
+ * 存在的账号时丢弃 —— 它只是个指针,留着会让兜底落到一个用不了的账号上。
1094
1111
  * @param parsed - 解析出的 JSON 值。
1095
1112
  * @returns 归一化后的账号集合。
1096
1113
  */
@@ -1106,22 +1123,17 @@ function normalizeStorage(parsed) {
1106
1123
  if (typeof value !== "string") return void 0;
1107
1124
  return accounts.some((a) => a.account.uid === value) ? value : void 0;
1108
1125
  };
1109
- const defaultUid = pick(record.defaultUid);
1110
1126
  const lastUid = pick(record.lastUid);
1111
1127
  return {
1112
1128
  accounts,
1113
- ...defaultUid === void 0 ? {} : { defaultUid },
1114
1129
  ...lastUid === void 0 ? {} : { lastUid }
1115
1130
  };
1116
1131
  }
1117
1132
  if (!isAccountEntry(record)) return emptyStorage();
1118
- return {
1119
- accounts: [{
1120
- auth: record.auth,
1121
- account: trimEmptyAccountFields(record.account)
1122
- }],
1123
- defaultUid: record.account.uid
1124
- };
1133
+ return { accounts: [{
1134
+ auth: record.auth,
1135
+ account: trimEmptyAccountFields(record.account)
1136
+ }] };
1125
1137
  }
1126
1138
  /**
1127
1139
  * 读取已存储的账号集合。
@@ -1168,11 +1180,10 @@ async function upsertAccount(entry) {
1168
1180
  await updateStorage((current) => {
1169
1181
  const uid = entry.account.uid;
1170
1182
  const accounts = current.accounts.some((a) => a.account.uid === uid) ? current.accounts.map((a) => a.account.uid === uid ? entry : a) : [...current.accounts, entry];
1171
- const defaultUid = current.defaultUid ?? (current.accounts.length === 0 ? uid : void 0);
1183
+ const lastUid = current.lastUid ?? (current.accounts.length === 0 ? uid : void 0);
1172
1184
  return {
1173
1185
  accounts,
1174
- ...defaultUid === void 0 ? {} : { defaultUid },
1175
- ...current.lastUid === void 0 ? {} : { lastUid: current.lastUid }
1186
+ ...lastUid === void 0 ? {} : { lastUid }
1176
1187
  };
1177
1188
  });
1178
1189
  }
@@ -1206,39 +1217,22 @@ async function reorderAccounts(uids) {
1206
1217
  });
1207
1218
  }
1208
1219
  /**
1209
- * 移除若干账号,并清掉指向它们的默认账号与“最后一次手动切换”的账号。
1220
+ * 移除若干账号,并清掉指向它们的“最后一次手动切换”的账号。
1210
1221
  * @param uids - 要移除的账号 uid。
1211
1222
  */
1212
1223
  async function removeAccounts(uids) {
1213
1224
  const removed = new Set(uids);
1214
1225
  await updateStorage((current) => {
1215
1226
  const accounts = current.accounts.filter((a) => !removed.has(a.account.uid));
1216
- const gone = (uid) => uid !== void 0 && removed.has(uid) ? void 0 : uid;
1217
- const defaultUid = gone(current.defaultUid);
1218
- const lastUid = gone(current.lastUid);
1227
+ const lastUid = current.lastUid !== void 0 && removed.has(current.lastUid) ? void 0 : current.lastUid;
1219
1228
  return {
1220
1229
  accounts,
1221
- ...defaultUid === void 0 ? {} : { defaultUid },
1222
1230
  ...lastUid === void 0 ? {} : { lastUid }
1223
1231
  };
1224
1232
  });
1225
1233
  }
1226
1234
  /**
1227
- * 指定或取消默认账号。
1228
- * @param uid - 默认账号的 uid;传 `undefined` 取消默认账号。
1229
- */
1230
- async function setDefaultAccount(uid) {
1231
- await updateStorage((current) => {
1232
- const next = {
1233
- accounts: current.accounts,
1234
- ...current.lastUid === void 0 ? {} : { lastUid: current.lastUid }
1235
- };
1236
- if (uid !== void 0 && current.accounts.some((a) => a.account.uid === uid)) next.defaultUid = uid;
1237
- return next;
1238
- });
1239
- }
1240
- /**
1241
- * 记下最后一次被手动切换到的账号(没有默认账号时作为兜底)。
1235
+ * 记下最后一次被手动切换到的账号,作为未选用账号的会话的兜底。
1242
1236
  * @param uid - 被切换到的账号 uid。
1243
1237
  */
1244
1238
  async function setLastAccount(uid) {
@@ -1273,7 +1267,8 @@ async function storageFreshness() {
1273
1267
  * 并发调用会互相取到对方的刷新结果,其中一个的 token 永远停留过期。
1274
1268
  *
1275
1269
  * 账号由会话决定:某个会话显式选了账号就用它,否则顺延到派生它的会话(子代理、
1276
- * fork)、默认账号、最后一次手动切换的账号,最后落到列表第一个。目录与计量快照
1270
+ * fork),再落到账号级兜底(最后一次手动切换的账号,其次是列表第一个)。会话在
1271
+ * 第一次真正发请求时把解析到的账号固定下来,此后不再跟随兜底。目录与计量快照
1277
1272
  * 也按账号分别缓存,因为不同账号的可用模型与额度本就可以不同。
1278
1273
  *
1279
1274
  * @module dsh-llm-codebuddy-power/session
@@ -1318,7 +1313,8 @@ function resolveCreditRecord(record, table) {
1318
1313
  }
1319
1314
  return {
1320
1315
  credit: record.credit,
1321
- accounts
1316
+ accounts,
1317
+ ...record.requestId === void 0 ? {} : { requestId: record.requestId }
1322
1318
  };
1323
1319
  }
1324
1320
  /**
@@ -1388,7 +1384,7 @@ var CodeBuddySession = class {
1388
1384
  *
1389
1385
  * 集合只由**带会话身份的**读取填充(聊天请求、模型面板),并供拿不到会话身份的
1390
1386
  * 调用方({@link knownModel}、`listModels`)查询:那些调用方无法知道该用哪个账号
1391
- * 的目录,主动读取只能落在默认账号上 —— 而当前会话用的可能正是另一个账号。
1387
+ * 的目录,主动读取只能落在账号级兜底上 —— 而当前会话用的可能正是另一个账号。
1392
1388
  */
1393
1389
  knownCatalog = /* @__PURE__ */ new Map();
1394
1390
  /** 按账号 uid 单飞的目录读取。 */
@@ -1505,12 +1501,14 @@ var CodeBuddySession = class {
1505
1501
  const existing = track.credits.get(turn);
1506
1502
  const accounts = existing?.accounts ?? [];
1507
1503
  const known = accounts.some((entry) => entry.uid === account.uid);
1504
+ const requestId = existing?.requestId ?? track.turnRequestId;
1508
1505
  const merged = {
1509
1506
  credit: (existing?.credit ?? 0) + credit,
1510
1507
  accounts: known ? accounts : [...accounts, {
1511
1508
  nickname: account.nickname,
1512
1509
  uid: account.uid
1513
- }]
1510
+ }],
1511
+ requestId
1514
1512
  };
1515
1513
  track.credits.set(turn, merged);
1516
1514
  updateSessionInfo(sessionId, (info) => {
@@ -1530,7 +1528,8 @@ var CodeBuddySession = class {
1530
1528
  ...info.credits,
1531
1529
  [String(turn)]: {
1532
1530
  credit: merged.credit,
1533
- accounts: keys
1531
+ accounts: keys,
1532
+ ...merged.requestId === void 0 ? {} : { requestId: merged.requestId }
1534
1533
  }
1535
1534
  }
1536
1535
  };
@@ -1579,7 +1578,7 @@ var CodeBuddySession = class {
1579
1578
  /**
1580
1579
  * 由当前缓存的各账号目录重建合并目录。
1581
1580
  *
1582
- * 账号退出后的处理必须是"重建"而不是"清空":清空会让官方列表塌回默认账号,
1581
+ * 账号退出后的处理必须是"重建"而不是"清空":清空会让官方列表塌回账号级兜底,
1583
1582
  * 而其余仍登录账号的目录本来就在手上 —— 继续并着才是"退出后重新合并当前已有的
1584
1583
  * 列表"。重建同时确定性地解决同 id 的归属:只剩下仍在的账号参与竞争。
1585
1584
  */
@@ -1615,7 +1614,7 @@ var CodeBuddySession = class {
1615
1614
  * 或每记一次“最后切换的账号”都会被当成账号变更,从而丢掉按账号缓存的计量快照并
1616
1615
  * 广播一次模型变更 —— 那些都不是账号增删。真的增删时才会丢弃已退出账号的缓存并
1617
1616
  * 重建合并目录:退出一个账号后官方列表应当继续并着其余账号已读到的目录,而不是
1618
- * 塌回默认账号一个。
1617
+ * 塌回账号级兜底那一个。
1619
1618
  */
1620
1619
  async refreshIfChanged() {
1621
1620
  const storageKey = await storageFreshness();
@@ -1637,8 +1636,9 @@ var CodeBuddySession = class {
1637
1636
  * 该会话自身及其派生祖先显式选用的账号,由近及远。读取某个会话的信息可能要碰盘,
1638
1637
  * 因此本方法是异步的。
1639
1638
  *
1640
- * 走到祖先是因为子代理与 fork 出来的会话在界面上没有自己的账号按钮:它们若不
1641
- * 沿用父会话,就会静默改用默认账号,把一次派发记到另一个账号头上。
1639
+ * 走到祖先是因为子代理与 fork 出来的会话在**固定之前**还没有自己的账号:子代理
1640
+ * 界面上从来就没有账号按钮,若不沿用父会话就会静默改用账号级兜底,把一次派发记到
1641
+ * 另一个账号头上;fork 有自己的按钮,但它在被切换之前也应当跟着父会话走。
1642
1642
  */
1643
1643
  async accountChain(sessionId) {
1644
1644
  const chain = [];
@@ -1655,17 +1655,39 @@ var CodeBuddySession = class {
1655
1655
  /**
1656
1656
  * 该会话此刻生效的账号 uid。
1657
1657
  *
1658
- * 顺序:会话(或其派生祖先)显式选用的账号 → 默认账号 → 最后一次手动切换的
1659
- * 账号 → 列表第一个。指向已退出账号的选择会被跳过,而不是当成“没有账号”;
1660
- * 全部落空时返回 `undefined`,调用方据此报未登录。
1658
+ * 顺序:会话(或其派生祖先)显式选用的账号 → 最后一次手动切换的账号 → 列表第一个。
1659
+ * 指向已退出账号的选择会被跳过,而不是当成“没有账号”;全部落空时返回
1660
+ * `undefined`,调用方据此报未登录。
1661
1661
  */
1662
1662
  async accountUidFor(sessionId) {
1663
1663
  if (sessionId !== void 0) {
1664
1664
  for (const uid of await this.accountChain(sessionId)) if (this.accountEntry(uid) !== void 0) return uid;
1665
1665
  }
1666
- for (const fallback of [this.storage?.defaultUid, this.storage?.lastUid]) if (this.accountEntry(fallback) !== void 0) return fallback;
1666
+ const last = this.storage?.lastUid;
1667
+ if (this.accountEntry(last) !== void 0) return last;
1667
1668
  return this.storage?.accounts[0]?.account.uid;
1668
1669
  }
1670
+ /**
1671
+ * 把该会话此刻解析到的账号写进它自己的文件,使它从此不再跟随账号级兜底。
1672
+ *
1673
+ * 已有选择就不再动它。那有两种可能,而两种都不该被覆盖:早已固定的会话重写只是
1674
+ * 同一份事实的重复落盘;而记录指向一个已退出账号时,读取侧按"没有选择"处理它,
1675
+ * 磁盘上的记录则只增不删 —— 覆盖掉它会让该账号重新登录后不再回到这个会话。
1676
+ * @param sessionId - dsh 会话身份。
1677
+ * @param uid - 本次请求实际使用的账号 uid。
1678
+ */
1679
+ async pinAccount(sessionId, uid) {
1680
+ const info = await this.infoOf(sessionId);
1681
+ if (info.uid !== void 0) return;
1682
+ await updateSessionInfo(sessionId, (current) => current.uid === void 0 ? {
1683
+ ...current,
1684
+ uid
1685
+ } : current);
1686
+ this.infos.set(sessionId, {
1687
+ ...info,
1688
+ uid
1689
+ });
1690
+ }
1669
1691
  identityOf(entry) {
1670
1692
  return {
1671
1693
  accessToken: entry.auth.accessToken,
@@ -1694,13 +1716,21 @@ var CodeBuddySession = class {
1694
1716
  }
1695
1717
  /**
1696
1718
  * 已存账号条目,在首次使用、失效之后以及文件被外部改动时从磁盘读取。
1697
- * @param sessionId - 用哪个会话的账号;会话未选用时按默认账号解析。
1719
+ *
1720
+ * 解析出的账号在返回前固定进该会话自己的文件,因此"这个会话用哪个账号"只在它
1721
+ * 第一次真正发请求时从全局兜底取一次;此后用户别处切换账号不会再改变它。固定
1722
+ * 挂在这里而不是账号解析处(见 {@link accountUidFor}),是因为本方法只被
1723
+ * {@link requestIdentity} 调用,即只有真实请求经过 —— 界面上打开一个空会话所
1724
+ * 触发的用量与目录读取不会落下任何记录。
1725
+ * @param sessionId - 用哪个会话的账号;会话未选用时按账号级兜底解析。
1698
1726
  * @throws NotLoggedInError 未存有任何凭据时。
1699
1727
  */
1700
1728
  async require(sessionId) {
1701
1729
  await this.refreshIfChanged();
1702
- const entry = this.accountEntry(await this.accountUidFor(sessionId));
1730
+ const uid = await this.accountUidFor(sessionId);
1731
+ const entry = this.accountEntry(uid);
1703
1732
  if (entry === void 0) throw new NotLoggedInError("CodeBuddy is not signed in. Run `dsh plugin --profile web codebuddy-power` to sign in through your browser; no API key is required.");
1733
+ if (sessionId !== void 0 && uid !== void 0) await this.pinAccount(sessionId, uid);
1704
1734
  return entry;
1705
1735
  }
1706
1736
  /**
@@ -1760,7 +1790,7 @@ var CodeBuddySession = class {
1760
1790
  * 两者在**同一次**身份解析中一起给出:分开解析会有两次账号选择,而账号是每次请求
1761
1791
  * 解析的(用户可在同一步里切换),两次解析可能落到不同账号上 —— 那样记进这一轮的
1762
1792
  * 账号名就与实际发出去的请求不符。缩写由落盘侧按同一规则算出,故此处给出完整账号。
1763
- * @param sessionId - 请求所属的会话;决定用哪个账号,缺省时用默认账号。
1793
+ * @param sessionId - 请求所属的会话;决定用哪个账号,缺省时按账号级兜底。
1764
1794
  * @returns 身份请求头与发起该请求的账号。
1765
1795
  */
1766
1796
  async requestIdentity(sessionId) {
@@ -1782,19 +1812,16 @@ var CodeBuddySession = class {
1782
1812
  return (this.storage?.accounts.length ?? 0) > 0;
1783
1813
  }
1784
1814
  /**
1785
- * 已存账号及其默认账号,供配置页与会话切换按钮展示。
1786
- * @returns 账号列表(顺序与磁盘一致)与默认账号 uid;未登录时列表为空。
1815
+ * 已存账号,供配置页与会话切换按钮展示。
1816
+ * @returns 账号列表(顺序与磁盘一致);未登录时列表为空。
1787
1817
  */
1788
1818
  async accountList() {
1789
1819
  await this.refreshIfChanged();
1790
- return {
1791
- accounts: (this.storage?.accounts ?? []).map(accountInfo),
1792
- ...this.storage?.defaultUid === void 0 ? {} : { defaultUid: this.storage.defaultUid }
1793
- };
1820
+ return (this.storage?.accounts ?? []).map(accountInfo);
1794
1821
  }
1795
1822
  /**
1796
1823
  * 某会话此刻生效的账号 uid。
1797
- * @param sessionId - dsh 会话身份;缺省时用默认账号。
1824
+ * @param sessionId - dsh 会话身份;缺省时按账号级兜底。
1798
1825
  * @returns 账号 uid;一个账号也没有时为 `undefined`。
1799
1826
  */
1800
1827
  async activeUid(sessionId) {
@@ -1803,6 +1830,10 @@ var CodeBuddySession = class {
1803
1830
  }
1804
1831
  /**
1805
1832
  * 把某会话的账号切换为指定账号,并把它记为最后一次手动切换的账号。
1833
+ *
1834
+ * 该会话已有持久记录才写它自己的文件;一个还从未落过盘的新会话只更新账号级的
1835
+ * `lastUid` —— 它可能永远不会被发过第一条消息,为它建文件会在磁盘上留下无人
1836
+ * 认领的残留文件。这类会话在真正发请求时由 {@link require} 固定下来。
1806
1837
  * @param sessionId - dsh 会话身份。
1807
1838
  * @param uid - 要使用的账号 uid。
1808
1839
  * @returns 该 uid 是已存账号时为 true;否则不做任何改动并返回 false。
@@ -1810,7 +1841,9 @@ var CodeBuddySession = class {
1810
1841
  async activate(sessionId, uid) {
1811
1842
  await this.refreshIfChanged();
1812
1843
  if (this.accountEntry(uid) === void 0) return false;
1844
+ await setLastAccount(uid);
1813
1845
  const info = await this.infoOf(sessionId);
1846
+ if (info.uid === void 0 && Object.keys(info.credits).length === 0) return true;
1814
1847
  await updateSessionInfo(sessionId, (current) => ({
1815
1848
  ...current,
1816
1849
  uid
@@ -1819,13 +1852,12 @@ var CodeBuddySession = class {
1819
1852
  ...info,
1820
1853
  uid
1821
1854
  });
1822
- await setLastAccount(uid);
1823
1855
  return true;
1824
1856
  }
1825
1857
  /**
1826
1858
  * CodeBuddy 模型目录,短暂缓存,并在并发读取者之间共享。
1827
1859
  * @param signal - 底层读取的可选取消信号。
1828
- * @param sessionId - 用哪个会话的账号;缺省时用默认账号。
1860
+ * @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
1829
1861
  * @returns 目录中的模型,按服务自身的顺序。
1830
1862
  */
1831
1863
  async models(signal, sessionId) {
@@ -1835,7 +1867,7 @@ var CodeBuddySession = class {
1835
1867
  * 目录及其推广与档位,在同一个 TTL 下按账号一起缓存,并按账号单飞,使列表与
1836
1868
  * 面板富化共享同一次读取。
1837
1869
  * @param signal - 底层读取的可选取消信号。
1838
- * @param sessionId - 用哪个会话的账号;缺省时用默认账号。
1870
+ * @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
1839
1871
  * @returns 模型、推广与档位。
1840
1872
  */
1841
1873
  async catalogData(signal, sessionId) {
@@ -1881,7 +1913,7 @@ var CodeBuddySession = class {
1881
1913
  * 从各账号已读到的目录的并集里查一个模型,**不发起任何请求**。
1882
1914
  *
1883
1915
  * 供拿不到会话身份的调用方使用(适配器的 `resolveModel`):它无法知道该用哪个
1884
- * 账号的目录,主动读取只能落在默认账号上,而当前会话用的可能正是另一个账号 ——
1916
+ * 账号的目录,主动读取只能落在账号级兜底上,而当前会话用的可能正是另一个账号 ——
1885
1917
  * 那会把一个账号独有的模型解析成保守的默认能力(上下文窗口、输出上限、图片输入
1886
1918
  * 全部按最保守处理)。并集由带会话身份的读取填充,因此这个查询零成本、也不改变
1887
1919
  * "谁在读目录"这件事。
@@ -1906,7 +1938,7 @@ var CodeBuddySession = class {
1906
1938
  * 目录及其推广/档位;读不到时返回空值 —— 供面板使用的
1907
1939
  * {@link catalogData} 建议性读取孪生体。
1908
1940
  * @param signal - 可选取消信号。
1909
- * @param sessionId - 用哪个会话的账号;缺省时用默认账号。
1941
+ * @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
1910
1942
  * @returns 目录与活动,或空列表。
1911
1943
  */
1912
1944
  async catalogDataOrEmpty(signal, sessionId) {
@@ -1933,7 +1965,7 @@ var CodeBuddySession = class {
1933
1965
  * 列出模型是配置页上的浏览动作,所以失败必须降级为“没有可显示的内容”,
1934
1966
  * 而不是让页面崩掉。请求路径直接使用 {@link models} 并保留真实失败。
1935
1967
  * @param signal - 可选取消信号。
1936
- * @param sessionId - 用哪个会话的账号;缺省时用默认账号。
1968
+ * @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
1937
1969
  * @returns 目录,或空列表。
1938
1970
  */
1939
1971
  async modelsOrEmpty(signal, sessionId) {
@@ -1954,7 +1986,7 @@ var CodeBuddySession = class {
1954
1986
  * 其他每种失败(传输、解析、刷新过期)都在一条警告之后解析为
1955
1987
  * `undefined`。身份通过与聊天请求相同的按账号单飞刷新解析,所以并发的计量
1956
1988
  * 读取绝不会消耗两次刷新 token。
1957
- * @param sessionId - 用哪个会话的账号;缺省时用默认账号。
1989
+ * @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
1958
1990
  * @param signal - 可选取消信号。
1959
1991
  * @returns 该快照;未存有任何凭据或计量平面不可达时为 `undefined`。
1960
1992
  */
@@ -3552,7 +3584,7 @@ function enrichModel(model, promotions, tiers, withOverrides) {
3552
3584
  * `startLogin` 生成握手并返回用户必须打开的 URL,`pollLogin` 检查该握手是否已完成,
3553
3585
  * `pendingLogin` 报出仍在轮询的握手(浏览器刷新后据此接回);
3554
3586
  * 一次完成的登录会**追加**一个账号,而不是取代已有的。`accounts`、`activate`、
3555
- * `setDefault`、`logout` 是配置页与会话账号按钮其余时间驱动的读写组合。
3587
+ * `logout`、`reorder` 是配置页与会话账号按钮其余时间驱动的读写组合。
3556
3588
  *
3557
3589
  * @module dsh-llm-codebuddy-power/auth-service
3558
3590
  */
@@ -3594,6 +3626,7 @@ const CREDIT_UID_DISPLAY_CHARS = 8;
3594
3626
  *
3595
3627
  * uid 在**呈现边界**才截断:存储与解析都保留完整值,截断只影响这一个提示行。把它放在
3596
3628
  * 这里而不是存储层,是因为存储层丢掉的信息无法再恢复,而显示层的取舍可以随时改。
3629
+ * 请求 id 不截断,因为它要整个被复制出去。
3597
3630
  * @param record - 已按账号表展开的轮次记录。
3598
3631
  * @returns 对客户端安全的投影。
3599
3632
  */
@@ -3603,7 +3636,8 @@ function projectCreditRecord(record) {
3603
3636
  accounts: record.accounts.map((account) => ({
3604
3637
  nickname: account.nickname,
3605
3638
  uidPrefix: account.uid.slice(0, CREDIT_UID_DISPLAY_CHARS)
3606
- }))
3639
+ })),
3640
+ ...record.requestId === void 0 ? {} : { requestId: record.requestId }
3607
3641
  };
3608
3642
  }
3609
3643
  /**
@@ -3650,10 +3684,6 @@ var CodeBuddyAuthService = class {
3650
3684
  case "cancelLogin": return this.cancelLogin(stringField(payload, "state")) ? ok(void 0) : err("bad-request", "a login handshake id is required");
3651
3685
  case "sessionAccount": return ok(await this.sessionAccount(stringField(payload, "sessionId")));
3652
3686
  case "activate": return await this.activate(stringField(payload, "sessionId"), stringField(payload, "uid")) ? ok(void 0) : err("unknown-account", "the requested account is not stored");
3653
- case "setDefault": {
3654
- const uid = stringField(payload, "uid");
3655
- return await this.setDefault(uid.length === 0 ? void 0 : uid) ? ok(void 0) : err("unknown-account", "the requested account is not stored");
3656
- }
3657
3687
  case "logout":
3658
3688
  await this.logout(stringListField(payload, "uids"));
3659
3689
  return ok(void 0);
@@ -3676,12 +3706,12 @@ var CodeBuddyAuthService = class {
3676
3706
  }
3677
3707
  }
3678
3708
  /**
3679
- * 读取已存账号及其默认账号,不要求必须存在。
3709
+ * 读取已存账号,不要求必须存在。
3680
3710
  * @returns 账号集合;未存任何内容时为空列表。
3681
3711
  */
3682
3712
  async accounts() {
3683
3713
  if (this.session === void 0) return { accounts: [] };
3684
- return this.session.accountList();
3714
+ return { accounts: await this.session.accountList() };
3685
3715
  }
3686
3716
  /**
3687
3717
  * 发起一次浏览器登录握手。
@@ -3789,19 +3819,6 @@ var CodeBuddyAuthService = class {
3789
3819
  return true;
3790
3820
  }
3791
3821
  /**
3792
- * 指定或取消默认账号。
3793
- * @param uid - 默认账号 uid;`undefined` 表示取消默认账号。
3794
- * @returns 指定时该账号已存、或取消成功时为 true。
3795
- */
3796
- async setDefault(uid) {
3797
- if (uid !== void 0) {
3798
- if (!(await this.accounts()).accounts.some((account) => account.uid === uid)) return false;
3799
- }
3800
- await setDefaultAccount(uid);
3801
- this.session?.invalidate();
3802
- return true;
3803
- }
3804
- /**
3805
3822
  * 移除若干账号。
3806
3823
  * @param uids - 要移除的账号 uid。
3807
3824
  */
@@ -3813,7 +3830,7 @@ var CodeBuddyAuthService = class {
3813
3830
  /**
3814
3831
  * 按给定顺序重排账号。
3815
3832
  *
3816
- * 顺序即展示顺序,也是没有默认账号、也没有手动切换记录时的兜底账号,因此这里真的
3833
+ * 顺序即展示顺序,也是没有手动切换记录时的兜底账号,因此这里真的
3817
3834
  * 改变"用谁"。重排会让输入区控件组读到的账号集合顺序变化,故同样使会话缓存失效。
3818
3835
  * @param uids - 目标顺序上的账号 uid。
3819
3836
  */
@@ -3893,7 +3910,7 @@ var CodeBuddyAuthService = class {
3893
3910
  }
3894
3911
  /**
3895
3912
  * 供客户端面板使用的增强版 CodeBuddy 目录:应用了促销/等级展示信息与覆盖配置的模型。
3896
- * @param sessionId - 用哪个会话的账号读目录;为空时用默认账号。
3913
+ * @param sessionId - 用哪个会话的账号读目录;为空时按账号级兜底。
3897
3914
  * @returns 增强结果;会话不可用或该账号没有目录时为空行。
3898
3915
  */
3899
3916
  async enrichedModels(sessionId) {
@@ -4103,4 +4120,4 @@ function apply(ctx, config) {
4103
4120
  }).catch(() => {});
4104
4121
  }
4105
4122
  //#endregion
4106
- export { AUTH_PENDING_CODE, BLOCKED_MODEL_IDS, CODEBUDDY_AUTH_CHANNEL, CODEBUDDY_AXIOS_USER_AGENT, CODEBUDDY_CHAT_BASE, CODEBUDDY_DISPLAY_NAME, CODEBUDDY_ENDPOINT, CODEBUDDY_IDE_VERSION, CODEBUDDY_PRODUCT_NAME, CODEBUDDY_PROVIDER, CodeBuddyAdapter, CodeBuddyAuthService, CodeBuddySession, Config, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, LOGIN_POLL_INTERVAL_MS, LOGIN_RESULT_RETENTION_MS, LOGIN_TIMEOUT_MS, NotLoggedInError, REQUEST_IMAGE_DIMENSION_LADDER, REQUEST_IMAGE_MAX_BYTES, apply, fetchEnterpriseUsage, fetchPersonalUsage, fetchUsage, getStoragePath, hasDisclosedCapacity, httpErrorCode, inject, isBlockedModelId, loadSessionInfo, loadStorage, login, name, parseUsage, removeAccounts, resolveConnectionOptions, resolvePrefs, setDefaultAccount, setLastAccount, updateSessionInfo, updateStorage, upsertAccount };
4123
+ export { AUTH_PENDING_CODE, BLOCKED_MODEL_IDS, CODEBUDDY_AUTH_CHANNEL, CODEBUDDY_AXIOS_USER_AGENT, CODEBUDDY_CHAT_BASE, CODEBUDDY_DISPLAY_NAME, CODEBUDDY_ENDPOINT, CODEBUDDY_GIFT_SUB_PRODUCT_CODE, CODEBUDDY_IDE_VERSION, CODEBUDDY_PRODUCT_NAME, CODEBUDDY_PROVIDER, CodeBuddyAdapter, CodeBuddyAuthService, CodeBuddySession, Config, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_TOKENS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, LOGIN_POLL_INTERVAL_MS, LOGIN_RESULT_RETENTION_MS, LOGIN_TIMEOUT_MS, NotLoggedInError, REQUEST_IMAGE_DIMENSION_LADDER, REQUEST_IMAGE_MAX_BYTES, apply, fetchEnterpriseUsage, fetchPersonalUsage, fetchUsage, getStoragePath, hasDisclosedCapacity, httpErrorCode, inject, isBlockedModelId, loadSessionInfo, loadStorage, login, name, parseUsage, removeAccounts, resolveConnectionOptions, resolvePrefs, setLastAccount, updateSessionInfo, updateStorage, upsertAccount };
@@ -5,7 +5,7 @@
5
5
  * `startLogin` 生成握手并返回用户必须打开的 URL,`pollLogin` 检查该握手是否已完成,
6
6
  * `pendingLogin` 报出仍在轮询的握手(浏览器刷新后据此接回);
7
7
  * 一次完成的登录会**追加**一个账号,而不是取代已有的。`accounts`、`activate`、
8
- * `setDefault`、`logout` 是配置页与会话账号按钮其余时间驱动的读写组合。
8
+ * `logout`、`reorder` 是配置页与会话账号按钮其余时间驱动的读写组合。
9
9
  *
10
10
  * @module dsh-llm-codebuddy-power/auth-service
11
11
  */
@@ -19,8 +19,6 @@ export declare const CODEBUDDY_AUTH_CHANNEL = "/codebuddy";
19
19
  export interface CodeBuddyAccountsResult {
20
20
  /** 已存账号,顺序与磁盘一致。 */
21
21
  accounts: CodeBuddyAccountInfo[];
22
- /** 用户显式指定的默认账号;未指定或缺省时缺省。 */
23
- defaultUid?: string;
24
22
  }
25
23
  /** `startLogin` 返回给客户端的结构。 */
26
24
  export interface CodeBuddyLoginStart {
@@ -89,6 +87,13 @@ export interface CodeBuddyTurnCreditResult {
89
87
  credit: number;
90
88
  /** 本轮扣分用过的账号,按首次使用排序;一轮里切换过账号时会有多个。 */
91
89
  accounts: CodeBuddyTurnCreditAccount[];
90
+ /**
91
+ * 本轮的 CodeBuddy 请求 id,完整值。
92
+ *
93
+ * 与 uid 不同,它不在这里截断:客户端要把它整个复制出来,截断就会复制到一个查不到的
94
+ * id。显示几位由呈现层决定,那里才是可以改的取舍。
95
+ */
96
+ requestId?: string;
92
97
  }
93
98
  /** `creditOf` 返回给客户端的一个账号。 */
94
99
  export interface CodeBuddyTurnCreditAccount {
@@ -116,7 +121,7 @@ export declare class CodeBuddyAuthService {
116
121
  /** 把一次 RPC endpoint 调用路由到对应的处理器。 */
117
122
  private dispatch;
118
123
  /**
119
- * 读取已存账号及其默认账号,不要求必须存在。
124
+ * 读取已存账号,不要求必须存在。
120
125
  * @returns 账号集合;未存任何内容时为空列表。
121
126
  */
122
127
  accounts(): Promise<CodeBuddyAccountsResult>;
@@ -174,12 +179,6 @@ export declare class CodeBuddyAuthService {
174
179
  * @returns 该握手是否已被放弃(未知 state 同样是 true)。
175
180
  */
176
181
  cancelLogin(state: string): boolean;
177
- /**
178
- * 指定或取消默认账号。
179
- * @param uid - 默认账号 uid;`undefined` 表示取消默认账号。
180
- * @returns 指定时该账号已存、或取消成功时为 true。
181
- */
182
- setDefault(uid: string | undefined): Promise<boolean>;
183
182
  /**
184
183
  * 移除若干账号。
185
184
  * @param uids - 要移除的账号 uid。
@@ -188,7 +187,7 @@ export declare class CodeBuddyAuthService {
188
187
  /**
189
188
  * 按给定顺序重排账号。
190
189
  *
191
- * 顺序即展示顺序,也是没有默认账号、也没有手动切换记录时的兜底账号,因此这里真的
190
+ * 顺序即展示顺序,也是没有手动切换记录时的兜底账号,因此这里真的
192
191
  * 改变"用谁"。重排会让输入区控件组读到的账号集合顺序变化,故同样使会话缓存失效。
193
192
  * @param uids - 目标顺序上的账号 uid。
194
193
  */
@@ -225,7 +224,7 @@ export declare class CodeBuddyAuthService {
225
224
  private settle;
226
225
  /**
227
226
  * 供客户端面板使用的增强版 CodeBuddy 目录:应用了促销/等级展示信息与覆盖配置的模型。
228
- * @param sessionId - 用哪个会话的账号读目录;为空时用默认账号。
227
+ * @param sessionId - 用哪个会话的账号读目录;为空时按账号级兜底。
229
228
  * @returns 增强结果;会话不可用或该账号没有目录时为空行。
230
229
  */
231
230
  enrichedModels(sessionId: string): Promise<{
@@ -9,8 +9,7 @@
9
9
  *
10
10
  * 用法:
11
11
  * codebuddy-power 登录并追加账号
12
- * codebuddy-power --status 列出全部账号与默认账号
13
- * codebuddy-power --default <账号> 把某个账号设为默认
12
+ * codebuddy-power --status 列出全部账号
14
13
  * codebuddy-power --logout <账号> 退出一个账号
15
14
  * codebuddy-power --logout --all 退出全部账号
16
15
  * codebuddy-power --no-open 只打印 URL,不打开浏览器
@@ -27,10 +27,9 @@ export interface AccountSwitcherInjected {
27
27
  /** 三个界面偏好的当前值,与设置页同源。 */
28
28
  prefs: HostObservable<CodeBuddyPrefs>;
29
29
  };
30
- /** 读取账号集合与默认账号。 */
30
+ /** 读取账号集合。 */
31
31
  fetchAccounts: () => Promise<{
32
32
  accounts: CodeBuddyAccountView[];
33
- defaultUid: string | null;
34
33
  } | null>;
35
34
  /** 读取该会话此刻生效的账号 uid;没有账号时为 null。 */
36
35
  fetchSessionAccount: () => Promise<string | null>;