dsh-account-pool 0.1.0 → 0.1.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/LICENSE +21 -0
- package/README.md +72 -3
- package/lib/index.js +53 -27
- package/package.json +3 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cliii-one
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -355,8 +355,77 @@ npm run check
|
|
|
355
355
|
- 账号池状态(冷却、熔断计数)是进程内状态,重启后重置
|
|
356
356
|
- 本插件只负责「接入与调度」,不做自动签到、成长任务等上游活动
|
|
357
357
|
|
|
358
|
+
## 参考与致谢
|
|
359
|
+
|
|
360
|
+
本项目的上游协议细节(端点、请求头、字段名、刷新契约、解密方式)
|
|
361
|
+
不是猜出来的,而是在这四个项目的实测结论基础上做的。**没有它们,
|
|
362
|
+
就没有这个插件**——尤其是那些"看起来奇怪但必须这样写"的地方,
|
|
363
|
+
每一处背后都有人踩过坑并记录下来。
|
|
364
|
+
|
|
365
|
+
### [workbuddy2api-panel](https://github.com/linguo2625469/workbuddy2api-panel)
|
|
366
|
+
|
|
367
|
+
WorkBuddy(CodeBuddy)网关面板,Go 实现。
|
|
368
|
+
|
|
369
|
+
| 用到的部分 | 在哪个文件 |
|
|
370
|
+
|---|---|
|
|
371
|
+
| 请求头形态(`X-CodeBuddy-Request`、设备标识派生规则) | `lib/headers.js` |
|
|
372
|
+
| 选号策略(三因子加权 + Top-5 抽签) | `lib/selector.js` |
|
|
373
|
+
| 用量统计口径 | `lib/usage.js` |
|
|
374
|
+
| 积分包解析(`CapacityRemain` / 周期包三档判定) | `lib/upstream.js` |
|
|
375
|
+
|
|
376
|
+
### [dsh-connect-workbuddy](https://github.com/dingminhua/dsh-connect-workbuddy)
|
|
377
|
+
|
|
378
|
+
同样把 WorkBuddy 接进 DSH 的插件,安全设计上做了正确的示范。
|
|
379
|
+
|
|
380
|
+
| 用到的部分 | 在哪个文件 |
|
|
381
|
+
|---|---|
|
|
382
|
+
| shim 安全模型(回环绑定、随机端口、进程内 secret、Host 校验) | `lib/shim.js` |
|
|
383
|
+
|
|
384
|
+
> 安全代码不做"改善"——这是它验证过的形态,照做。
|
|
385
|
+
|
|
386
|
+
### [dsh-connect-trae](https://github.com/dingminhua/dsh-connect-trae)
|
|
387
|
+
|
|
388
|
+
Trae 接入 DSH 的插件,带完整取证文档(`docs/` 下有实测记录)。
|
|
389
|
+
|
|
390
|
+
| 用到的部分 | 在哪个文件 |
|
|
391
|
+
|---|---|
|
|
392
|
+
| 上游常量(host、`APP_ID`、`IDE_VERSION`) | `lib/trae-accounts.js` |
|
|
393
|
+
| 刷新契约(按 region 分流的 ClientID 与端点) | `lib/trae-accounts.js` |
|
|
394
|
+
| SSE 协议转换(Trae 命名事件 → OpenAI chunk) | `lib/trae-bridge.js` |
|
|
395
|
+
| 目录与通道不一致的取证(`glm-5.3` 为何不可调用) | `lib/trae-upstream.js` |
|
|
396
|
+
| 设备身份策略(导入时生成一次、复用而非每请求重新生成) | `lib/trae-accounts.js` |
|
|
397
|
+
|
|
398
|
+
### [traework2api](https://github.com/Sliverkiss/traework2api)
|
|
399
|
+
|
|
400
|
+
Trae 网关,Go 实现,登录脚本带有最完整的实测结论。
|
|
401
|
+
|
|
402
|
+
| 用到的部分 | 在哪个文件 |
|
|
403
|
+
|---|---|
|
|
404
|
+
| 凭据解密(`storage.json` 的 AES-CBC 盐值与布局) | `lib/trae-storage.js` |
|
|
405
|
+
| 签到流程与必需的 `X-Device-Id` | `lib/trae-accounts.js` |
|
|
406
|
+
|
|
407
|
+
### 这些参考带来的具体判断
|
|
408
|
+
|
|
409
|
+
有些"为什么不那样写"的答案来自这些项目:
|
|
410
|
+
|
|
411
|
+
- **`glm-5.3` 在目录里但不能用** —— Trae 的目录(`get_detail_param`)与调用通道
|
|
412
|
+
(`llm_utils_chat`)是两个面,前者是全量、后者只服务子集
|
|
413
|
+
- **Trae 必须做协议转换** —— 它的 SSE 是命名事件格式,pi-ai 只认 OpenAI 格式,
|
|
414
|
+
直通会撑到超时(表现为"很慢、几乎都失败")
|
|
415
|
+
- **签到要带 `X-Device-Id`** —— 缺了回业务码 `9004`,HTTP 却是 200
|
|
416
|
+
- **周期积分包不能靠 `CapacityType` 枚举判定** —— 要用三档数据驱动,
|
|
417
|
+
否则"刚好用完"的包会算错
|
|
418
|
+
|
|
358
419
|
## 免责声明
|
|
359
420
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
421
|
+
本项目仅供个人学习和研究使用,仅驱动使用者自己的账号。
|
|
422
|
+
|
|
423
|
+
使用者需遵守所接入服务(WorkBuddy / CodeBuddy、Trae)的服务条款;
|
|
424
|
+
因使用本项目产生的任何后果由使用者自行承担。
|
|
425
|
+
|
|
426
|
+
本项目与腾讯、字节跳动、WorkBuddy、CodeBuddy、Trae、DeepSeek 均无关联,
|
|
427
|
+
未获得上述任何一方的授权或认可。
|
|
428
|
+
|
|
429
|
+
## 许可
|
|
430
|
+
|
|
431
|
+
[MIT](LICENSE) © 2026 cliii-one
|
package/lib/index.js
CHANGED
|
@@ -299,22 +299,41 @@ async function createRegionStack({ region, dshHome, config, logger, ctx }) {
|
|
|
299
299
|
let creditsCache = { at: 0, value: null }
|
|
300
300
|
|
|
301
301
|
/** 查各账号积分(带缓存);单个账号失败不影响其他账号。 */
|
|
302
|
+
/**
|
|
303
|
+
* 查本区域各账号的积分(带 60 秒缓存)。
|
|
304
|
+
*
|
|
305
|
+
* 账号之间**并行**查询:每个账号是一次独立的上游请求,串行会让
|
|
306
|
+
* 设置页的等待时间等于所有账号之和(实测 3 个账号串行约 1.5s)。
|
|
307
|
+
* 并行后约等于最慢的那个。
|
|
308
|
+
*
|
|
309
|
+
* 单个账号失败只丢它自己的数据,不影响其他账号。
|
|
310
|
+
*/
|
|
302
311
|
const loadCredits = async (force = false) => {
|
|
303
312
|
const now = Date.now()
|
|
304
313
|
if (!force && creditsCache.value !== null && now - creditsCache.at < 60_000) {
|
|
305
314
|
return creditsCache.value
|
|
306
315
|
}
|
|
307
|
-
const
|
|
308
|
-
|
|
316
|
+
const accounts = await store.list()
|
|
317
|
+
|
|
318
|
+
/** 查一个账号的积分;失败返回 undefined 而不是抛错。 */
|
|
319
|
+
const queryOne = async (account) => {
|
|
309
320
|
try {
|
|
310
321
|
const credential = await store.usable(account.id)
|
|
311
|
-
|
|
322
|
+
const credits = await fetchRegionCredits(credential)
|
|
312
323
|
// 顺手把总积分喂给选号器,让加权用上真实余额。
|
|
313
|
-
picker.setCredits(account.id,
|
|
324
|
+
picker.setCredits(account.id, credits.total)
|
|
325
|
+
return [account.id, credits]
|
|
314
326
|
} catch (error) {
|
|
315
327
|
logger.warn(`账号 ${account.id} 查询积分失败:${error?.message ?? error}`)
|
|
328
|
+
return undefined
|
|
316
329
|
}
|
|
317
330
|
}
|
|
331
|
+
|
|
332
|
+
const settled = await Promise.all(accounts.map(queryOne))
|
|
333
|
+
const out = {}
|
|
334
|
+
for (const entry of settled) {
|
|
335
|
+
if (entry !== undefined) out[entry[0]] = entry[1]
|
|
336
|
+
}
|
|
318
337
|
creditsCache = { at: now, value: out }
|
|
319
338
|
return out
|
|
320
339
|
}
|
|
@@ -625,34 +644,41 @@ export function apply(ctx, config = {}) {
|
|
|
625
644
|
if (!loopbackOrigin(req)) { json(res, 403, { ok: false, error: '仅允许本机访问' }); return }
|
|
626
645
|
try {
|
|
627
646
|
const list = await ready
|
|
628
|
-
|
|
629
|
-
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* 取一个区域的状态。
|
|
650
|
+
*
|
|
651
|
+
* 区域之间**并行**:cn 与 trae 是两套独立上游,串行会让设置页
|
|
652
|
+
* 的等待时间变成两者之和(冷缓存实测约 1.5s);并行后约等于
|
|
653
|
+
* 最慢的那个。
|
|
654
|
+
*/
|
|
655
|
+
const regionState = async (stack) => {
|
|
630
656
|
const snapshot = stack.picker.snapshot()
|
|
631
|
-
|
|
657
|
+
const listed = await stack.store.list()
|
|
658
|
+
const credits = await stack.loadCredits()
|
|
659
|
+
return [stack.region, {
|
|
632
660
|
provider: stack.providerId,
|
|
633
661
|
displayName: REGION_DISPLAY_NAMES[stack.region],
|
|
634
662
|
models: stack.getCatalog().length,
|
|
635
|
-
accounts: (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
creditPackages: credits[account.id]?.packages?.slice(0, 5) ?? [],
|
|
651
|
-
}))
|
|
652
|
-
})()),
|
|
653
|
-
}
|
|
663
|
+
accounts: listed.map(account => ({
|
|
664
|
+
id: account.id,
|
|
665
|
+
nickname: account.nickname,
|
|
666
|
+
uid: account.uid,
|
|
667
|
+
domain: account.domain,
|
|
668
|
+
expiresAtMs: account.expiresAtMs,
|
|
669
|
+
...snapshot[account.id],
|
|
670
|
+
cooling: snapshot[account.id]?.cooling ?? false,
|
|
671
|
+
// 积分:剩余 + 总量 + 套餐明细(界面展示用)。
|
|
672
|
+
// size 供悬停提示展示「总量 / 已用 / 剩余」构成。
|
|
673
|
+
credits: credits[account.id]?.total,
|
|
674
|
+
creditsSize: credits[account.id]?.size,
|
|
675
|
+
creditPackages: credits[account.id]?.packages?.slice(0, 5) ?? [],
|
|
676
|
+
})),
|
|
677
|
+
}]
|
|
654
678
|
}
|
|
655
|
-
|
|
679
|
+
|
|
680
|
+
const settled = await Promise.all(list.map(regionState))
|
|
681
|
+
json(res, 200, { ok: true, regions: Object.fromEntries(settled) })
|
|
656
682
|
} catch (error) {
|
|
657
683
|
json(res, 500, { ok: false, error: safeMessage(error) })
|
|
658
684
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-account-pool",
|
|
3
3
|
"displayName": "Account Pool",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "把多个 WorkBuddy / Trae 账号汇成账号池接入 DeepSeek Harness:自动切换、限流熔断退避、凭证安全落盘",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"lib",
|
|
28
28
|
"cordis.patch.yml",
|
|
29
|
-
"README.md"
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
30
31
|
],
|
|
31
32
|
"engines": {
|
|
32
33
|
"node": "^22.19.0 || >=24.0.0"
|