dsh-llm-codebuddy-power 1.0.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/LICENSE +22 -22
- package/README.md +84 -66
- package/README.zh.md +84 -66
- package/cordis.patch.yml +7 -7
- package/icon.svg +6 -0
- package/lib/cli-login.js +113 -110
- package/lib/client.js +491 -410
- package/lib/index.js +439 -273
- package/lib/types/adapter.d.ts +7 -1
- package/lib/types/auth-service.d.ts +68 -22
- package/lib/types/cli/login.d.ts +1 -2
- package/lib/types/client/AccountSwitcher.d.ts +1 -2
- package/lib/types/client/CodeBuddySection.d.ts +17 -12
- package/lib/types/client/TurnCreditPill.d.ts +5 -4
- package/lib/types/client/index.d.ts +6 -6
- package/lib/types/client/locales.d.ts +6 -4
- package/lib/types/client/store.d.ts +19 -13
- package/lib/types/client/wire.d.ts +20 -3
- package/lib/types/constants.d.ts +15 -0
- package/lib/types/index.d.ts +44 -22
- package/lib/types/prefs.d.ts +17 -13
- package/lib/types/serialize.d.ts +19 -10
- package/lib/types/session-store.d.ts +1 -1
- package/lib/types/session.d.ts +59 -29
- package/lib/types/settings.d.ts +10 -7
- package/lib/types/storage.d.ts +6 -13
- package/lib/types/usage.d.ts +4 -2
- package/package.json +37 -30
- package/lib/types/client/UsageIndicator.d.ts +0 -21
- package/lib/types/client/poll.d.ts +0 -19
- package/lib/types/credit-store.d.ts +0 -25
- package/lib/types/selection.d.ts +0 -35
package/lib/cli-login.js
CHANGED
|
@@ -354,7 +354,7 @@ async function writeJsonAtomic(path, value) {
|
|
|
354
354
|
* (`$DSH_HOME/data/plugins/llm-codebuddy-power/`,通过 harness 所用的同一个
|
|
355
355
|
* `@deepseek-ai/dsh-home-paths` 解析),而不放在插件包内,因此重装不会让
|
|
356
356
|
* 用户退出登录。写入经 [atomic-file.ts](./atomic-file.ts) 做原子替换并按路径串行化:
|
|
357
|
-
*
|
|
357
|
+
* 多个账号的令牌刷新、配置页的增删与 CLI 的调用都在改同一份文件,不串行化时
|
|
358
358
|
* 两个并发写入会各自基于自己读到的旧内容覆盖对方。残缺文件会让用户手里只有
|
|
359
359
|
* 一份读不出的凭据,还无法与“从未登录过”区分开。
|
|
360
360
|
*
|
|
@@ -429,9 +429,8 @@ function trimEmptyAccountFields(account) {
|
|
|
429
429
|
* 把解析出的 JSON 归一化为账号集合。
|
|
430
430
|
*
|
|
431
431
|
* 同时接受当前形态与仅有一个账号的旧形态(顶层 `auth` + `account`):旧文件
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* 指针,留着会让“有没有默认账号”与实际情况相反。
|
|
432
|
+
* 迁移成单元素列表,于是升级不会改变用户下一次请求用的是谁。`lastUid` 指向已不
|
|
433
|
+
* 存在的账号时丢弃 —— 它只是个指针,留着会让兜底落到一个用不了的账号上。
|
|
435
434
|
* @param parsed - 解析出的 JSON 值。
|
|
436
435
|
* @returns 归一化后的账号集合。
|
|
437
436
|
*/
|
|
@@ -447,22 +446,17 @@ function normalizeStorage(parsed) {
|
|
|
447
446
|
if (typeof value !== "string") return void 0;
|
|
448
447
|
return accounts.some((a) => a.account.uid === value) ? value : void 0;
|
|
449
448
|
};
|
|
450
|
-
const defaultUid = pick(record.defaultUid);
|
|
451
449
|
const lastUid = pick(record.lastUid);
|
|
452
450
|
return {
|
|
453
451
|
accounts,
|
|
454
|
-
...defaultUid === void 0 ? {} : { defaultUid },
|
|
455
452
|
...lastUid === void 0 ? {} : { lastUid }
|
|
456
453
|
};
|
|
457
454
|
}
|
|
458
455
|
if (!isAccountEntry(record)) return emptyStorage();
|
|
459
|
-
return {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}],
|
|
464
|
-
defaultUid: record.account.uid
|
|
465
|
-
};
|
|
456
|
+
return { accounts: [{
|
|
457
|
+
auth: record.auth,
|
|
458
|
+
account: trimEmptyAccountFields(record.account)
|
|
459
|
+
}] };
|
|
466
460
|
}
|
|
467
461
|
/**
|
|
468
462
|
* 读取已存储的账号集合。
|
|
@@ -502,55 +496,37 @@ async function updateStorage(change) {
|
|
|
502
496
|
* 插入一个账号,或替换同 uid 账号的凭据。
|
|
503
497
|
*
|
|
504
498
|
* 同 uid 视为重新登录同一个账号:令牌被替换而不是追加成第二个条目,否则
|
|
505
|
-
*
|
|
499
|
+
* 配置页会出现两个同名徽章,退出其中一个还会留下另一个。
|
|
506
500
|
* @param entry - 刚登录得到的账号。
|
|
507
501
|
*/
|
|
508
502
|
async function upsertAccount(entry) {
|
|
509
503
|
await updateStorage((current) => {
|
|
510
504
|
const uid = entry.account.uid;
|
|
511
505
|
const accounts = current.accounts.some((a) => a.account.uid === uid) ? current.accounts.map((a) => a.account.uid === uid ? entry : a) : [...current.accounts, entry];
|
|
512
|
-
const
|
|
506
|
+
const lastUid = current.lastUid ?? (current.accounts.length === 0 ? uid : void 0);
|
|
513
507
|
return {
|
|
514
508
|
accounts,
|
|
515
|
-
...
|
|
516
|
-
...current.lastUid === void 0 ? {} : { lastUid: current.lastUid }
|
|
509
|
+
...lastUid === void 0 ? {} : { lastUid }
|
|
517
510
|
};
|
|
518
511
|
});
|
|
519
512
|
}
|
|
520
513
|
/**
|
|
521
|
-
*
|
|
514
|
+
* 移除若干账号,并清掉指向它们的“最后一次手动切换”的账号。
|
|
522
515
|
* @param uids - 要移除的账号 uid。
|
|
523
516
|
*/
|
|
524
517
|
async function removeAccounts(uids) {
|
|
525
518
|
const removed = new Set(uids);
|
|
526
519
|
await updateStorage((current) => {
|
|
527
520
|
const accounts = current.accounts.filter((a) => !removed.has(a.account.uid));
|
|
528
|
-
const
|
|
529
|
-
const defaultUid = gone(current.defaultUid);
|
|
530
|
-
const lastUid = gone(current.lastUid);
|
|
521
|
+
const lastUid = current.lastUid !== void 0 && removed.has(current.lastUid) ? void 0 : current.lastUid;
|
|
531
522
|
return {
|
|
532
523
|
accounts,
|
|
533
|
-
...defaultUid === void 0 ? {} : { defaultUid },
|
|
534
524
|
...lastUid === void 0 ? {} : { lastUid }
|
|
535
525
|
};
|
|
536
526
|
});
|
|
537
527
|
}
|
|
538
528
|
/**
|
|
539
|
-
*
|
|
540
|
-
* @param uid - 默认账号的 uid;传 `undefined` 取消默认账号。
|
|
541
|
-
*/
|
|
542
|
-
async function setDefaultAccount(uid) {
|
|
543
|
-
await updateStorage((current) => {
|
|
544
|
-
const next = {
|
|
545
|
-
accounts: current.accounts,
|
|
546
|
-
...current.lastUid === void 0 ? {} : { lastUid: current.lastUid }
|
|
547
|
-
};
|
|
548
|
-
if (uid !== void 0 && current.accounts.some((a) => a.account.uid === uid)) next.defaultUid = uid;
|
|
549
|
-
return next;
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* 记下最后一次被手动切换到的账号(没有默认账号时作为兜底)。
|
|
529
|
+
* 记下最后一次被手动切换到的账号,作为未选用账号的会话的兜底。
|
|
554
530
|
* @param uid - 被切换到的账号 uid。
|
|
555
531
|
*/
|
|
556
532
|
async function setLastAccount(uid) {
|
|
@@ -800,16 +776,19 @@ function normalizeResetTime(raw) {
|
|
|
800
776
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
801
777
|
}
|
|
802
778
|
/**
|
|
803
|
-
*
|
|
779
|
+
* 判断某个计量窗口是否是一次性赠送额度。
|
|
804
780
|
*
|
|
805
|
-
*
|
|
806
|
-
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
*
|
|
781
|
+
* 依据是子产品编码:{@link CODEBUDDY_GIFT_SUB_PRODUCT_CODE} 由全部赠送活动共用,
|
|
782
|
+
* 而可续期主套餐用另一个编码。套餐显示名不能独立作依据 —— 两类赠送活动的
|
|
783
|
+
* `PackageCode` 不同、显示名也不同,只有一类名字里带“裂变”,另一类叫“拉新权益包”,
|
|
784
|
+
* 按名匹配会把后者错算进主池。显示名与子产品名因此只作兜底,供未来换了子产品编码
|
|
785
|
+
* 却仍在名字里沿用“裂变/赠送”字样的赠送活动使用。
|
|
786
|
+
* @param resource - `Accounts` 数组里的一项。
|
|
787
|
+
* @returns 赠送窗口返回 true。
|
|
810
788
|
*/
|
|
811
|
-
function
|
|
812
|
-
|
|
789
|
+
function isGiftResource(resource) {
|
|
790
|
+
if (string(resource, "SubProductCode") === "sp_tcaca_codebuddyide_bonus_pack") return true;
|
|
791
|
+
return /裂变|赠送/.test(string(resource, "PackageName") ?? "") || /裂变|赠送/.test(string(resource, "SubProductName") ?? "");
|
|
813
792
|
}
|
|
814
793
|
/**
|
|
815
794
|
* 把有可用容量的窗口聚合成一个池(used/limit 求和)。
|
|
@@ -838,13 +817,13 @@ function liveGiftWindows(windows) {
|
|
|
838
817
|
/**
|
|
839
818
|
* 构建个人快照的主池/赠送池划分,以及提示框的赠送明细(最早过期的有效赠送
|
|
840
819
|
* 在前,最多 5 个)。
|
|
841
|
-
* @param windows -
|
|
820
|
+
* @param windows - 已解析的窗口,各自带 {@link UsageWindow.gift} 归属。
|
|
842
821
|
* @returns 该划分,仅当对应池有可用 limit 时才包含 `main`/`gift`。
|
|
843
822
|
*/
|
|
844
823
|
function splitPools(windows) {
|
|
845
824
|
const mainWindows = [];
|
|
846
825
|
const giftWindows = [];
|
|
847
|
-
for (const window of windows) if (
|
|
826
|
+
for (const window of windows) if (window.gift) giftWindows.push(window);
|
|
848
827
|
else mainWindows.push(window);
|
|
849
828
|
const main = poolOf(mainWindows);
|
|
850
829
|
const live = liveGiftWindows(giftWindows);
|
|
@@ -883,14 +862,17 @@ function personalUsage(accounts) {
|
|
|
883
862
|
const name = string(resource, "PackageName") ?? string(resource, "PackageCode") ?? string(resource, "ResourceId") ?? `resource_${index}`;
|
|
884
863
|
const rawReset = string(resource, "CycleEndTime");
|
|
885
864
|
const resetsAt = rawReset === void 0 ? void 0 : normalizeResetTime(rawReset);
|
|
865
|
+
const gift = isGiftResource(resource);
|
|
886
866
|
if (limit <= 0) return {
|
|
887
867
|
name,
|
|
868
|
+
gift,
|
|
888
869
|
...resetsAt === void 0 ? {} : { resetsAt }
|
|
889
870
|
};
|
|
890
871
|
return {
|
|
891
872
|
name,
|
|
892
873
|
used,
|
|
893
874
|
limit,
|
|
875
|
+
gift,
|
|
894
876
|
...resetsAt === void 0 ? {} : { resetsAt }
|
|
895
877
|
};
|
|
896
878
|
});
|
|
@@ -920,6 +902,7 @@ function enterpriseUsage(data) {
|
|
|
920
902
|
name: "enterprise",
|
|
921
903
|
used,
|
|
922
904
|
limit,
|
|
905
|
+
gift: false,
|
|
923
906
|
...reset === void 0 ? {} : { resetsAt: reset }
|
|
924
907
|
};
|
|
925
908
|
const main = poolOf([window]);
|
|
@@ -1113,10 +1096,14 @@ function isAccountRef(value) {
|
|
|
1113
1096
|
/**
|
|
1114
1097
|
* 文件读出的记录是否合法。
|
|
1115
1098
|
*
|
|
1116
|
-
* `
|
|
1117
|
-
*
|
|
1118
|
-
*
|
|
1119
|
-
*
|
|
1099
|
+
* `credit` 与 `accounts` 同为必填:一条记录说不出这一轮扣了多少、用的是哪个账号,
|
|
1100
|
+
* 就答不了它存在的唯一问题,与"没有这条记录"没有区别,因此不完整即整条丢弃。引用
|
|
1101
|
+
* 还必须能在同一份文件的账号表里解析到 —— 一个悬空引用渲染不出账号,与缺字段是同
|
|
1102
|
+
* 一种残缺。
|
|
1103
|
+
*
|
|
1104
|
+
* `requestId` 则允许缺失:它是后加的字段,已发布的旧版本写下的记录没有它。那些轮次
|
|
1105
|
+
* 照常显示积分,只是拿不出请求 id,因此不值得为它丢掉整轮积分。存在时必须是字符串,
|
|
1106
|
+
* 否则是损坏内容而不是旧格式。
|
|
1120
1107
|
* @param value - 文件里的一条轮次记录。
|
|
1121
1108
|
* @param table - 同一份文件的账号表。
|
|
1122
1109
|
* @returns 该记录是否可用。
|
|
@@ -1124,7 +1111,7 @@ function isAccountRef(value) {
|
|
|
1124
1111
|
function isCreditRecord(value, table) {
|
|
1125
1112
|
if (typeof value !== "object" || value === null) return false;
|
|
1126
1113
|
const record = value;
|
|
1127
|
-
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]));
|
|
1114
|
+
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");
|
|
1128
1115
|
}
|
|
1129
1116
|
/**
|
|
1130
1117
|
* 读取一个会话的信息。
|
|
@@ -1212,7 +1199,8 @@ async function updateSessionInfo(sessionId, change) {
|
|
|
1212
1199
|
* 并发调用会互相取到对方的刷新结果,其中一个的 token 永远停留过期。
|
|
1213
1200
|
*
|
|
1214
1201
|
* 账号由会话决定:某个会话显式选了账号就用它,否则顺延到派生它的会话(子代理、
|
|
1215
|
-
* fork)
|
|
1202
|
+
* fork),再落到账号级兜底(最后一次手动切换的账号,其次是列表第一个)。会话在
|
|
1203
|
+
* 第一次真正发请求时把解析到的账号固定下来,此后不再跟随兜底。目录与计量快照
|
|
1216
1204
|
* 也按账号分别缓存,因为不同账号的可用模型与额度本就可以不同。
|
|
1217
1205
|
*
|
|
1218
1206
|
* @module dsh-llm-codebuddy-power/session
|
|
@@ -1257,7 +1245,8 @@ function resolveCreditRecord(record, table) {
|
|
|
1257
1245
|
}
|
|
1258
1246
|
return {
|
|
1259
1247
|
credit: record.credit,
|
|
1260
|
-
accounts
|
|
1248
|
+
accounts,
|
|
1249
|
+
...record.requestId === void 0 ? {} : { requestId: record.requestId }
|
|
1261
1250
|
};
|
|
1262
1251
|
}
|
|
1263
1252
|
/**
|
|
@@ -1327,7 +1316,7 @@ var CodeBuddySession = class {
|
|
|
1327
1316
|
*
|
|
1328
1317
|
* 集合只由**带会话身份的**读取填充(聊天请求、模型面板),并供拿不到会话身份的
|
|
1329
1318
|
* 调用方({@link knownModel}、`listModels`)查询:那些调用方无法知道该用哪个账号
|
|
1330
|
-
*
|
|
1319
|
+
* 的目录,主动读取只能落在账号级兜底上 —— 而当前会话用的可能正是另一个账号。
|
|
1331
1320
|
*/
|
|
1332
1321
|
knownCatalog = /* @__PURE__ */ new Map();
|
|
1333
1322
|
/** 按账号 uid 单飞的目录读取。 */
|
|
@@ -1444,12 +1433,14 @@ var CodeBuddySession = class {
|
|
|
1444
1433
|
const existing = track.credits.get(turn);
|
|
1445
1434
|
const accounts = existing?.accounts ?? [];
|
|
1446
1435
|
const known = accounts.some((entry) => entry.uid === account.uid);
|
|
1436
|
+
const requestId = existing?.requestId ?? track.turnRequestId;
|
|
1447
1437
|
const merged = {
|
|
1448
1438
|
credit: (existing?.credit ?? 0) + credit,
|
|
1449
1439
|
accounts: known ? accounts : [...accounts, {
|
|
1450
1440
|
nickname: account.nickname,
|
|
1451
1441
|
uid: account.uid
|
|
1452
|
-
}]
|
|
1442
|
+
}],
|
|
1443
|
+
requestId
|
|
1453
1444
|
};
|
|
1454
1445
|
track.credits.set(turn, merged);
|
|
1455
1446
|
updateSessionInfo(sessionId, (info) => {
|
|
@@ -1469,7 +1460,8 @@ var CodeBuddySession = class {
|
|
|
1469
1460
|
...info.credits,
|
|
1470
1461
|
[String(turn)]: {
|
|
1471
1462
|
credit: merged.credit,
|
|
1472
|
-
accounts: keys
|
|
1463
|
+
accounts: keys,
|
|
1464
|
+
...merged.requestId === void 0 ? {} : { requestId: merged.requestId }
|
|
1473
1465
|
}
|
|
1474
1466
|
}
|
|
1475
1467
|
};
|
|
@@ -1518,7 +1510,7 @@ var CodeBuddySession = class {
|
|
|
1518
1510
|
/**
|
|
1519
1511
|
* 由当前缓存的各账号目录重建合并目录。
|
|
1520
1512
|
*
|
|
1521
|
-
* 账号退出后的处理必须是"重建"而不是"清空"
|
|
1513
|
+
* 账号退出后的处理必须是"重建"而不是"清空":清空会让官方列表塌回账号级兜底,
|
|
1522
1514
|
* 而其余仍登录账号的目录本来就在手上 —— 继续并着才是"退出后重新合并当前已有的
|
|
1523
1515
|
* 列表"。重建同时确定性地解决同 id 的归属:只剩下仍在的账号参与竞争。
|
|
1524
1516
|
*/
|
|
@@ -1554,7 +1546,7 @@ var CodeBuddySession = class {
|
|
|
1554
1546
|
* 或每记一次“最后切换的账号”都会被当成账号变更,从而丢掉按账号缓存的计量快照并
|
|
1555
1547
|
* 广播一次模型变更 —— 那些都不是账号增删。真的增删时才会丢弃已退出账号的缓存并
|
|
1556
1548
|
* 重建合并目录:退出一个账号后官方列表应当继续并着其余账号已读到的目录,而不是
|
|
1557
|
-
*
|
|
1549
|
+
* 塌回账号级兜底那一个。
|
|
1558
1550
|
*/
|
|
1559
1551
|
async refreshIfChanged() {
|
|
1560
1552
|
const storageKey = await storageFreshness();
|
|
@@ -1576,8 +1568,9 @@ var CodeBuddySession = class {
|
|
|
1576
1568
|
* 该会话自身及其派生祖先显式选用的账号,由近及远。读取某个会话的信息可能要碰盘,
|
|
1577
1569
|
* 因此本方法是异步的。
|
|
1578
1570
|
*
|
|
1579
|
-
* 走到祖先是因为子代理与 fork
|
|
1580
|
-
*
|
|
1571
|
+
* 走到祖先是因为子代理与 fork 出来的会话在**固定之前**还没有自己的账号:子代理
|
|
1572
|
+
* 界面上从来就没有账号按钮,若不沿用父会话就会静默改用账号级兜底,把一次派发记到
|
|
1573
|
+
* 另一个账号头上;fork 有自己的按钮,但它在被切换之前也应当跟着父会话走。
|
|
1581
1574
|
*/
|
|
1582
1575
|
async accountChain(sessionId) {
|
|
1583
1576
|
const chain = [];
|
|
@@ -1594,17 +1587,39 @@ var CodeBuddySession = class {
|
|
|
1594
1587
|
/**
|
|
1595
1588
|
* 该会话此刻生效的账号 uid。
|
|
1596
1589
|
*
|
|
1597
|
-
* 顺序:会话(或其派生祖先)显式选用的账号 →
|
|
1598
|
-
*
|
|
1599
|
-
*
|
|
1590
|
+
* 顺序:会话(或其派生祖先)显式选用的账号 → 最后一次手动切换的账号 → 列表第一个。
|
|
1591
|
+
* 指向已退出账号的选择会被跳过,而不是当成“没有账号”;全部落空时返回
|
|
1592
|
+
* `undefined`,调用方据此报未登录。
|
|
1600
1593
|
*/
|
|
1601
1594
|
async accountUidFor(sessionId) {
|
|
1602
1595
|
if (sessionId !== void 0) {
|
|
1603
1596
|
for (const uid of await this.accountChain(sessionId)) if (this.accountEntry(uid) !== void 0) return uid;
|
|
1604
1597
|
}
|
|
1605
|
-
|
|
1598
|
+
const last = this.storage?.lastUid;
|
|
1599
|
+
if (this.accountEntry(last) !== void 0) return last;
|
|
1606
1600
|
return this.storage?.accounts[0]?.account.uid;
|
|
1607
1601
|
}
|
|
1602
|
+
/**
|
|
1603
|
+
* 把该会话此刻解析到的账号写进它自己的文件,使它从此不再跟随账号级兜底。
|
|
1604
|
+
*
|
|
1605
|
+
* 已有选择就不再动它。那有两种可能,而两种都不该被覆盖:早已固定的会话重写只是
|
|
1606
|
+
* 同一份事实的重复落盘;而记录指向一个已退出账号时,读取侧按"没有选择"处理它,
|
|
1607
|
+
* 磁盘上的记录则只增不删 —— 覆盖掉它会让该账号重新登录后不再回到这个会话。
|
|
1608
|
+
* @param sessionId - dsh 会话身份。
|
|
1609
|
+
* @param uid - 本次请求实际使用的账号 uid。
|
|
1610
|
+
*/
|
|
1611
|
+
async pinAccount(sessionId, uid) {
|
|
1612
|
+
const info = await this.infoOf(sessionId);
|
|
1613
|
+
if (info.uid !== void 0) return;
|
|
1614
|
+
await updateSessionInfo(sessionId, (current) => current.uid === void 0 ? {
|
|
1615
|
+
...current,
|
|
1616
|
+
uid
|
|
1617
|
+
} : current);
|
|
1618
|
+
this.infos.set(sessionId, {
|
|
1619
|
+
...info,
|
|
1620
|
+
uid
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1608
1623
|
identityOf(entry) {
|
|
1609
1624
|
return {
|
|
1610
1625
|
accessToken: entry.auth.accessToken,
|
|
@@ -1633,13 +1648,21 @@ var CodeBuddySession = class {
|
|
|
1633
1648
|
}
|
|
1634
1649
|
/**
|
|
1635
1650
|
* 已存账号条目,在首次使用、失效之后以及文件被外部改动时从磁盘读取。
|
|
1636
|
-
*
|
|
1651
|
+
*
|
|
1652
|
+
* 解析出的账号在返回前固定进该会话自己的文件,因此"这个会话用哪个账号"只在它
|
|
1653
|
+
* 第一次真正发请求时从全局兜底取一次;此后用户别处切换账号不会再改变它。固定
|
|
1654
|
+
* 挂在这里而不是账号解析处(见 {@link accountUidFor}),是因为本方法只被
|
|
1655
|
+
* {@link requestIdentity} 调用,即只有真实请求经过 —— 界面上打开一个空会话所
|
|
1656
|
+
* 触发的用量与目录读取不会落下任何记录。
|
|
1657
|
+
* @param sessionId - 用哪个会话的账号;会话未选用时按账号级兜底解析。
|
|
1637
1658
|
* @throws NotLoggedInError 未存有任何凭据时。
|
|
1638
1659
|
*/
|
|
1639
1660
|
async require(sessionId) {
|
|
1640
1661
|
await this.refreshIfChanged();
|
|
1641
|
-
const
|
|
1662
|
+
const uid = await this.accountUidFor(sessionId);
|
|
1663
|
+
const entry = this.accountEntry(uid);
|
|
1642
1664
|
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.");
|
|
1665
|
+
if (sessionId !== void 0 && uid !== void 0) await this.pinAccount(sessionId, uid);
|
|
1643
1666
|
return entry;
|
|
1644
1667
|
}
|
|
1645
1668
|
/**
|
|
@@ -1699,7 +1722,7 @@ var CodeBuddySession = class {
|
|
|
1699
1722
|
* 两者在**同一次**身份解析中一起给出:分开解析会有两次账号选择,而账号是每次请求
|
|
1700
1723
|
* 解析的(用户可在同一步里切换),两次解析可能落到不同账号上 —— 那样记进这一轮的
|
|
1701
1724
|
* 账号名就与实际发出去的请求不符。缩写由落盘侧按同一规则算出,故此处给出完整账号。
|
|
1702
|
-
* @param sessionId -
|
|
1725
|
+
* @param sessionId - 请求所属的会话;决定用哪个账号,缺省时按账号级兜底。
|
|
1703
1726
|
* @returns 身份请求头与发起该请求的账号。
|
|
1704
1727
|
*/
|
|
1705
1728
|
async requestIdentity(sessionId) {
|
|
@@ -1721,19 +1744,16 @@ var CodeBuddySession = class {
|
|
|
1721
1744
|
return (this.storage?.accounts.length ?? 0) > 0;
|
|
1722
1745
|
}
|
|
1723
1746
|
/**
|
|
1724
|
-
*
|
|
1725
|
-
* @returns 账号列表(顺序与磁盘一致)
|
|
1747
|
+
* 已存账号,供配置页与会话切换按钮展示。
|
|
1748
|
+
* @returns 账号列表(顺序与磁盘一致);未登录时列表为空。
|
|
1726
1749
|
*/
|
|
1727
1750
|
async accountList() {
|
|
1728
1751
|
await this.refreshIfChanged();
|
|
1729
|
-
return
|
|
1730
|
-
accounts: (this.storage?.accounts ?? []).map(accountInfo),
|
|
1731
|
-
...this.storage?.defaultUid === void 0 ? {} : { defaultUid: this.storage.defaultUid }
|
|
1732
|
-
};
|
|
1752
|
+
return (this.storage?.accounts ?? []).map(accountInfo);
|
|
1733
1753
|
}
|
|
1734
1754
|
/**
|
|
1735
1755
|
* 某会话此刻生效的账号 uid。
|
|
1736
|
-
* @param sessionId - dsh
|
|
1756
|
+
* @param sessionId - dsh 会话身份;缺省时按账号级兜底。
|
|
1737
1757
|
* @returns 账号 uid;一个账号也没有时为 `undefined`。
|
|
1738
1758
|
*/
|
|
1739
1759
|
async activeUid(sessionId) {
|
|
@@ -1742,6 +1762,10 @@ var CodeBuddySession = class {
|
|
|
1742
1762
|
}
|
|
1743
1763
|
/**
|
|
1744
1764
|
* 把某会话的账号切换为指定账号,并把它记为最后一次手动切换的账号。
|
|
1765
|
+
*
|
|
1766
|
+
* 该会话已有持久记录才写它自己的文件;一个还从未落过盘的新会话只更新账号级的
|
|
1767
|
+
* `lastUid` —— 它可能永远不会被发过第一条消息,为它建文件会在磁盘上留下无人
|
|
1768
|
+
* 认领的残留文件。这类会话在真正发请求时由 {@link require} 固定下来。
|
|
1745
1769
|
* @param sessionId - dsh 会话身份。
|
|
1746
1770
|
* @param uid - 要使用的账号 uid。
|
|
1747
1771
|
* @returns 该 uid 是已存账号时为 true;否则不做任何改动并返回 false。
|
|
@@ -1749,7 +1773,9 @@ var CodeBuddySession = class {
|
|
|
1749
1773
|
async activate(sessionId, uid) {
|
|
1750
1774
|
await this.refreshIfChanged();
|
|
1751
1775
|
if (this.accountEntry(uid) === void 0) return false;
|
|
1776
|
+
await setLastAccount(uid);
|
|
1752
1777
|
const info = await this.infoOf(sessionId);
|
|
1778
|
+
if (info.uid === void 0 && Object.keys(info.credits).length === 0) return true;
|
|
1753
1779
|
await updateSessionInfo(sessionId, (current) => ({
|
|
1754
1780
|
...current,
|
|
1755
1781
|
uid
|
|
@@ -1758,13 +1784,12 @@ var CodeBuddySession = class {
|
|
|
1758
1784
|
...info,
|
|
1759
1785
|
uid
|
|
1760
1786
|
});
|
|
1761
|
-
await setLastAccount(uid);
|
|
1762
1787
|
return true;
|
|
1763
1788
|
}
|
|
1764
1789
|
/**
|
|
1765
1790
|
* CodeBuddy 模型目录,短暂缓存,并在并发读取者之间共享。
|
|
1766
1791
|
* @param signal - 底层读取的可选取消信号。
|
|
1767
|
-
* @param sessionId -
|
|
1792
|
+
* @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
|
|
1768
1793
|
* @returns 目录中的模型,按服务自身的顺序。
|
|
1769
1794
|
*/
|
|
1770
1795
|
async models(signal, sessionId) {
|
|
@@ -1774,7 +1799,7 @@ var CodeBuddySession = class {
|
|
|
1774
1799
|
* 目录及其推广与档位,在同一个 TTL 下按账号一起缓存,并按账号单飞,使列表与
|
|
1775
1800
|
* 面板富化共享同一次读取。
|
|
1776
1801
|
* @param signal - 底层读取的可选取消信号。
|
|
1777
|
-
* @param sessionId -
|
|
1802
|
+
* @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
|
|
1778
1803
|
* @returns 模型、推广与档位。
|
|
1779
1804
|
*/
|
|
1780
1805
|
async catalogData(signal, sessionId) {
|
|
@@ -1820,7 +1845,7 @@ var CodeBuddySession = class {
|
|
|
1820
1845
|
* 从各账号已读到的目录的并集里查一个模型,**不发起任何请求**。
|
|
1821
1846
|
*
|
|
1822
1847
|
* 供拿不到会话身份的调用方使用(适配器的 `resolveModel`):它无法知道该用哪个
|
|
1823
|
-
*
|
|
1848
|
+
* 账号的目录,主动读取只能落在账号级兜底上,而当前会话用的可能正是另一个账号 ——
|
|
1824
1849
|
* 那会把一个账号独有的模型解析成保守的默认能力(上下文窗口、输出上限、图片输入
|
|
1825
1850
|
* 全部按最保守处理)。并集由带会话身份的读取填充,因此这个查询零成本、也不改变
|
|
1826
1851
|
* "谁在读目录"这件事。
|
|
@@ -1845,7 +1870,7 @@ var CodeBuddySession = class {
|
|
|
1845
1870
|
* 目录及其推广/档位;读不到时返回空值 —— 供面板使用的
|
|
1846
1871
|
* {@link catalogData} 建议性读取孪生体。
|
|
1847
1872
|
* @param signal - 可选取消信号。
|
|
1848
|
-
* @param sessionId -
|
|
1873
|
+
* @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
|
|
1849
1874
|
* @returns 目录与活动,或空列表。
|
|
1850
1875
|
*/
|
|
1851
1876
|
async catalogDataOrEmpty(signal, sessionId) {
|
|
@@ -1869,10 +1894,10 @@ var CodeBuddySession = class {
|
|
|
1869
1894
|
/**
|
|
1870
1895
|
* 目录;读不到时返回空列表。
|
|
1871
1896
|
*
|
|
1872
|
-
*
|
|
1897
|
+
* 列出模型是配置页上的浏览动作,所以失败必须降级为“没有可显示的内容”,
|
|
1873
1898
|
* 而不是让页面崩掉。请求路径直接使用 {@link models} 并保留真实失败。
|
|
1874
1899
|
* @param signal - 可选取消信号。
|
|
1875
|
-
* @param sessionId -
|
|
1900
|
+
* @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
|
|
1876
1901
|
* @returns 目录,或空列表。
|
|
1877
1902
|
*/
|
|
1878
1903
|
async modelsOrEmpty(signal, sessionId) {
|
|
@@ -1893,7 +1918,7 @@ var CodeBuddySession = class {
|
|
|
1893
1918
|
* 其他每种失败(传输、解析、刷新过期)都在一条警告之后解析为
|
|
1894
1919
|
* `undefined`。身份通过与聊天请求相同的按账号单飞刷新解析,所以并发的计量
|
|
1895
1920
|
* 读取绝不会消耗两次刷新 token。
|
|
1896
|
-
* @param sessionId -
|
|
1921
|
+
* @param sessionId - 用哪个会话的账号;缺省时按账号级兜底解析。
|
|
1897
1922
|
* @param signal - 可选取消信号。
|
|
1898
1923
|
* @returns 该快照;未存有任何凭据或计量平面不可达时为 `undefined`。
|
|
1899
1924
|
*/
|
|
@@ -1936,8 +1961,7 @@ var CodeBuddySession = class {
|
|
|
1936
1961
|
*
|
|
1937
1962
|
* 用法:
|
|
1938
1963
|
* codebuddy-power 登录并追加账号
|
|
1939
|
-
* codebuddy-power --status
|
|
1940
|
-
* codebuddy-power --default <账号> 把某个账号设为默认
|
|
1964
|
+
* codebuddy-power --status 列出全部账号
|
|
1941
1965
|
* codebuddy-power --logout <账号> 退出一个账号
|
|
1942
1966
|
* codebuddy-power --logout --all 退出全部账号
|
|
1943
1967
|
* codebuddy-power --no-open 只打印 URL,不打开浏览器
|
|
@@ -1967,10 +1991,9 @@ function lookupAccount(accounts, spec) {
|
|
|
1967
1991
|
if (byNickname.length > 1) return { failure: `The nickname "${spec}" matches ${byNickname.length} accounts; use a uid or an index instead.` };
|
|
1968
1992
|
return { failure: `No stored account matches "${spec}". Run --status to list them.` };
|
|
1969
1993
|
}
|
|
1970
|
-
/**
|
|
1994
|
+
/** 打印全部账号,再列出兜底账号可用的模型。 */
|
|
1971
1995
|
async function status() {
|
|
1972
|
-
const
|
|
1973
|
-
const accounts = stored.accounts;
|
|
1996
|
+
const accounts = (await loadStorage()).accounts;
|
|
1974
1997
|
if (accounts.length === 0) {
|
|
1975
1998
|
console.log("Not signed in. Run `dsh plugin --profile web codebuddy-power` to sign in through your browser.");
|
|
1976
1999
|
return 1;
|
|
@@ -1978,8 +2001,7 @@ async function status() {
|
|
|
1978
2001
|
console.log(`Accounts (${accounts.length}):`);
|
|
1979
2002
|
accounts.forEach((account, index) => {
|
|
1980
2003
|
const enterprise = account.account.enterpriseName === void 0 ? "" : ` [${account.account.enterpriseName}]`;
|
|
1981
|
-
|
|
1982
|
-
console.log(` ${index + 1}. ${account.account.nickname}${enterprise} (uid ${account.account.uid})${isDefault}`);
|
|
2004
|
+
console.log(` ${index + 1}. ${account.account.nickname}${enterprise} (uid ${account.account.uid})`);
|
|
1983
2005
|
});
|
|
1984
2006
|
console.log(`Credential: ${getStoragePath()}`);
|
|
1985
2007
|
const models = await new CodeBuddySession().modelsOrEmpty();
|
|
@@ -2001,17 +2023,6 @@ async function status() {
|
|
|
2001
2023
|
}
|
|
2002
2024
|
return 0;
|
|
2003
2025
|
}
|
|
2004
|
-
/** 设置默认账号。 */
|
|
2005
|
-
async function setDefault(spec) {
|
|
2006
|
-
const found = lookupAccount((await loadStorage()).accounts, spec);
|
|
2007
|
-
if ("failure" in found) {
|
|
2008
|
-
console.error(found.failure);
|
|
2009
|
-
return 1;
|
|
2010
|
-
}
|
|
2011
|
-
await setDefaultAccount(found.entry.account.uid);
|
|
2012
|
-
console.log(`Default account is now ${found.entry.account.nickname} (uid ${found.entry.account.uid}).`);
|
|
2013
|
-
return 0;
|
|
2014
|
-
}
|
|
2015
2026
|
/** 退出一个账号,或 `--all` 时退出全部。 */
|
|
2016
2027
|
async function logout(spec, all) {
|
|
2017
2028
|
const stored = await loadStorage();
|
|
@@ -2050,18 +2061,10 @@ async function main() {
|
|
|
2050
2061
|
return at === -1 ? void 0 : argv[at + 1];
|
|
2051
2062
|
};
|
|
2052
2063
|
if (args.has("--help") || args.has("-h")) {
|
|
2053
|
-
console.log("Usage: dsh plugin --profile web codebuddy-power [--status | --
|
|
2064
|
+
console.log("Usage: dsh plugin --profile web codebuddy-power [--status | --logout <account> | --logout --all | --no-open]");
|
|
2054
2065
|
return 0;
|
|
2055
2066
|
}
|
|
2056
2067
|
if (args.has("--status")) return status();
|
|
2057
|
-
if (args.has("--default")) {
|
|
2058
|
-
const spec = valueOf("--default");
|
|
2059
|
-
if (spec === void 0) {
|
|
2060
|
-
console.error("--default needs an account (uid, index, or nickname).");
|
|
2061
|
-
return 1;
|
|
2062
|
-
}
|
|
2063
|
-
return setDefault(spec);
|
|
2064
|
-
}
|
|
2065
2068
|
if (args.has("--logout")) return logout(valueOf("--logout"), args.has("--all"));
|
|
2066
2069
|
const controller = new AbortController();
|
|
2067
2070
|
const onSignal = () => {
|