magpie-ctyun 0.0.0-stage → 0.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/ACCOUNT_DELETION.md +62 -0
- package/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +200 -3
- package/SECURITY.md +25 -0
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/index.mjs +1 -0
- package/package.json +56 -4
- package/scripts/forget-credentials.mjs +9 -0
- package/src/browser.mjs +66 -0
- package/src/credentials.mjs +39 -0
- package/src/model-profiles.mjs +83 -0
- package/src/plugin.mjs +282 -0
- package/src/session.mjs +220 -0
- package/src/sse.mjs +119 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# 账号删除复现与修复(2026-10-31)
|
|
2
|
+
|
|
3
|
+
## 已确认的运行环境
|
|
4
|
+
|
|
5
|
+
- 当前应用:`/Applications/magpie.app`;本地目录导入本插件。
|
|
6
|
+
- 实际运行宿主:`host-57e4026712e7.js`,SHA-256 为 `57e4026712e728b64e7b7f3cb3c9bd56198e41c21647e14854e2ecf5f4617495`。
|
|
7
|
+
- 排查开始时有两个宿主,一个由当前 MAGPIE 启动,一个已成为孤立进程。多个宿主是并发参与者,但数量本身不足以证明恢复账号的原因。
|
|
8
|
+
- 复现使用全新临时存储,构造供应商主键和两个带 `#` 的槽位键,不复制真实认证文件、不访问钥匙串或真实天翼云接口。
|
|
9
|
+
|
|
10
|
+
## 复现证据
|
|
11
|
+
|
|
12
|
+
`scripts/host-account-regression.mjs` 启动两个未经修改的实际宿主,共用一个临时认证文件。测试入口包装真实插件,只将网络和凭据库替换为假会话;通过测试闸门控制并发顺序。
|
|
13
|
+
|
|
14
|
+
测试键固定为 `ctyun`(A)、`ctyun#fixture-b`(B)、`ctyun#fixture-c`(C),全部为虚构账号。
|
|
15
|
+
|
|
16
|
+
修改前,对工作区已有的防恢复补丁进行了以下复现:
|
|
17
|
+
|
|
18
|
+
1. A 的额度读取成功,`getAuth` 检查确认 A 尚在。
|
|
19
|
+
2. 在插件已调用 `client.auth.set`、宿主尚未实际写入时暂停测试包装层。
|
|
20
|
+
3. 第二个宿主收到 `signOut({ provider, account: A })`,文件只剩 B、C。
|
|
21
|
+
4. 放行旧查询的写入;文件重新包含 A、B、C。
|
|
22
|
+
5. 删除 B,文件留下 A、C;断言失败:`Deleted first account was restored by late auth.set`。
|
|
23
|
+
|
|
24
|
+
闸门是为确定性测试人为控制的调度点,不是生产宿主中的延迟。它证明跨宿主检查与写入不能作为一个原子操作;没有对真实账号做删除抓取,因此不能认定这是用户每一次界面异常的唯一原因。
|
|
25
|
+
|
|
26
|
+
另一个对照场景中,两个宿主无并发写回地连续删除 A、B,文件和 `providers.accounts` 均只剩 C。没有发现该场景把 A 删除解析为 B 或将槽位重新编号:`signOut` 使用传入的精确键;主键删除后,后缀键不迁移至主键。
|
|
27
|
+
|
|
28
|
+
## 根因与边界
|
|
29
|
+
|
|
30
|
+
实际宿主的 `makeClient().auth.set` 用 `AsyncLocalStorage` 找到当前账号键,再在 `changeAuth` 锁内执行 `all[key] = body`。锁防止整个文件互相覆盖,但它没有在锁内要求 `all[key]` 仍存在或仍是原会话。插件在锁外 `await getAuth()` 的预检查不能消除这个窗口。原先 `provider.models` 的 auth 快照写回也是同类风险;此前改为不写回,只消除了该钩子的路径。
|
|
31
|
+
|
|
32
|
+
实际宿主的原生续期路径不同:`renewLocked` 在异步 `auth.refresh` 返回后,于同一个 `changeAuth` 写锁内检查 `changed(all[key], was)`;已删除、重新登录或被另一宿主更新的账号不会被旧结果覆盖。测试也验证了删除与原生续期交错的行为。
|
|
33
|
+
|
|
34
|
+
此前 36 项单元测试以单变量模拟认证存储,没有运行真实宿主的多账号键、文件锁、AsyncLocalStorage、signOut 和原生续期;`host-smoke.mjs` 原来也只验证加载与注册。它们通过不代表实际删除流程已修复。
|
|
35
|
+
|
|
36
|
+
## 本插件修复
|
|
37
|
+
|
|
38
|
+
- `SessionManager` 不再接受或调用持久化回调,成功和失败的会话状态均保留在每账号内存缓存;仍串行处理同一账号操作、检查排队期间账号是否已更改。
|
|
39
|
+
- `provider.models`、`auth.usage` 均不再写认证存储。
|
|
40
|
+
- 只有登录结果和原生 `auth.refresh` 补丁交由宿主保存。本插件没有自行编辑认证文件,也没有通过另一套文件锁或文件监听回删恢复的账号。
|
|
41
|
+
- 模型 ID 的 `-oc` 映射与账号会话写回分开处理,模型发现不调用 `client.auth.set`。
|
|
42
|
+
|
|
43
|
+
限制:查询恢复的新 Cookie 尚未原生持久化时,如果宿主重启,该次内存状态会丢失,后续会从原持久化会话恢复;浏览器 IAM 会话也失效时仍需重新登录。不支持原生续期或没有相同原子检查的其他宿主,需要单独适配。
|
|
44
|
+
|
|
45
|
+
## 验证与安全处理
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npm test
|
|
49
|
+
npm run check
|
|
50
|
+
bun scripts/host-account-regression.mjs --host /path/to/host-57e4026712e7.js
|
|
51
|
+
bun scripts/host-smoke.mjs --host /path/to/host-57e4026712e7.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
当前回归包含单元测试、语法检查、实际宿主加载测试和 9 组实际宿主账号场景。账号场景覆盖仅 Key 接入及补登录、无浏览器/无凭据库时的不保存密码登录、切回仅 Key 和后缀账号升级,以及额度成功/失败、检查与写入交错、模型快照、原生续期期间删除账号;均验证临时文件中的实际键。
|
|
55
|
+
|
|
56
|
+
必须让全部旧宿主退出,再启动新的 MAGPIE 宿主;只编辑本地目录不会替换旧进程已经导入的模块。不要批量删除真实认证文件、用 `.bak` 覆盖现有文件,或自动移除“看起来重复”的记录。
|
|
57
|
+
|
|
58
|
+
本地验收已确认:退出旧进程并重启原应用后,仅有一个由应用管理的宿主,账号键、标签和 API Key 保持一致。没有替换应用二进制或宿主缓存源码。
|
|
59
|
+
|
|
60
|
+
需要对真实界面进一步验收时,先明确授权目标,再记录脱敏账号键的删除前后变化、刷新及重启后的变化。如仍恢复,须区分文件是否真的新增键,还是仅界面/`logins.json` 列表残留,并进一步排查其他旧宿主、同步恢复或宿主列表缓存。当前没有真实账号删除验收证据。
|
|
61
|
+
|
|
62
|
+
通用宿主修复应为异步读写上下文携带原会话身份,并在 `changeAuth` 锁内进行条件写入,同时保留首次登录可新建账号的行为。仅增加 `if (all[key])` 仍不能防止旧查询覆盖同一槽位的新登录。本插件已避开不安全接口,但不能替其他插件修复该宿主接口。
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 更新记录
|
|
2
|
+
|
|
3
|
+
## 0.3.0 — 2026-11-01
|
|
4
|
+
|
|
5
|
+
- 保留仅 API Key、浏览器单点登录、账号密码登录三个入口,在密码登录中增加“是否保存密码”的原生是/否选项。不保存时可在无浏览器、无系统凭据库的 headless/Docker 环境登录并查询额度,保存 Cookie/IAM 会话,IAM 失效后手动重登。
|
|
6
|
+
- 浏览器登录在无图形会话的 Linux 环境提前给出可用替代方式;启动窗口失败时区分桌面安装要求与服务器使用方式。
|
|
7
|
+
- 原登录方式索引 0/1/2 不变;调用登录接口时,仅显式传入 `save_password=yes` 才保存密码。已有保存密码的登录继续使用原凭据库;切换到不保存模式不删除旧条目。
|
|
8
|
+
- 增加无浏览器/无凭据库的登录、IAM 恢复、IAM 失效、验证码/短信中断和实际宿主账号更新测试。
|
|
9
|
+
- 验证码维持原来的中断和人工处理提示,未增加图片展示、输入界面或验证码提交接口。
|
|
10
|
+
|
|
11
|
+
## 0.2.1 — 2026-10-31
|
|
12
|
+
|
|
13
|
+
- 内置模型按原厂国际站官方价格(美元/百万 token)声明 `cost`,magpie 可估算费用,不再显示为 0。天翼云不公布按 token 的单价,此价格仅供参考,实际按套餐额度计费。
|
|
14
|
+
- 网页登录后的动态模型发现保留宿主已有的价格;宿主没有价格时使用内置价格。新发现的未知模型不填价格。
|
|
15
|
+
|
|
16
|
+
## 0.2.0 — 2026-10-31
|
|
17
|
+
|
|
18
|
+
- 新增「仅 API Key(不查询额度)」方式,无需网页登录;以后可用相同账号名称和原 Key 补充浏览器或密码登录,也可切回仅 Key 模式。
|
|
19
|
+
- 修复模型/额度异步查询可能恢复已删除账号的问题:查询只更新内存状态,持久化交给宿主原生续期的条件保存。
|
|
20
|
+
- 所有注册模型 ID 与显示名称去除末尾的 `-oc`,推理请求恢复准确的官方 ID;保留模型大小写、版本号及思考参数,动态映射按账号隔离。
|
|
21
|
+
- 原有加载器在账号被删除后停止发送请求,避免继续使用原 Key。
|
|
22
|
+
- 增加实际 MAGPIE 宿主的账号创建、补登录、模式切换及并发删除回归;全部使用临时认证存储和假会话。
|
|
23
|
+
|
|
24
|
+
升级时请将供应商勾选、客户端模型选择、保存的路由组成员中的旧 `-oc` ID 换成无后缀 ID。登录方式索引现在依次为:0 仅 Key、1 浏览器、2 密码;按数字选择方式的自动化脚本需同步更新。
|
|
25
|
+
|
|
26
|
+
模型自动分组仍按 MAGPIE 的规则处理版本差异;网页登录恢复的 Cookie 在宿主原生持久化前只保存在内存中。宿主要求和复现边界见 [账号删除说明](ACCOUNT_DELETION.md)。
|
|
27
|
+
|
|
28
|
+
## 0.1.0
|
|
29
|
+
|
|
30
|
+
- 初始版本:浏览器/密码登录、官方模型发现、三个原生额度窗口、模型思考参数与 SSE 兼容处理。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sheauhuu
|
|
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
|
@@ -1,3 +1,200 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# 天翼云 息壤(Coding Plan)
|
|
2
|
+
|
|
3
|
+
天翼云 Coding Plan 的 [magpie](https://usemagpie.ai/) 供应商插件。可仅使用 API Key 调用模型;需要官方模型发现和 5 小时/周/套餐总量三个额度窗口时,可选择密码登录(保存或不保存密码),有桌面的环境也可选择浏览器单点登录。
|
|
4
|
+
|
|
5
|
+
供应商显示名称为「天翼云息壤「ctyun」」,供应商 id 为 `ctyun`,npm 包名为 `magpie-ctyun`。图标采用 [eaichat.ctyun.cn 官网 favicon](https://eaichat.ctyun.cn/chat/favicon.ico) 的原始文件,嵌入插件元数据,显示时无需访问官网。当前 magpie 的“已安装插件”列表标题仍由宿主按包名或目录名生成。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
需要在运行 magpie 的主机/容器中安装插件。插件由 magpie 的 Bun 宿主运行;Node.js 22+ 用于本地开发和测试。
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install magpie-ctyun
|
|
13
|
+
magpie plugin add magpie-ctyun
|
|
14
|
+
magpie plugin login ctyun
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
或克隆本仓库本地安装:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
git clone https://github.com/sheauhuu/magpie-ctyun-coding-plan.git
|
|
21
|
+
cd magpie-ctyun-coding-plan
|
|
22
|
+
npm install
|
|
23
|
+
magpie plugin add "$PWD"
|
|
24
|
+
magpie plugin login ctyun
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
也可在 magpie 的「插件 → 添加插件」中填写本地仓库的绝对路径,然后选择供应商登录。
|
|
28
|
+
|
|
29
|
+
浏览器登录需要 magpie 所在主机有可操作的图形桌面,优先使用该主机已安装的 Chrome,但使用独立的临时登录上下文。没有 Chrome 时可安装专用 Chromium:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm run browser:install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
通过浏览器访问服务器的 magpie 页面,不会把官网认证窗口打开在客户端电脑上。无桌面的 Linux/Docker 可选择「账号密码登录」,在“是否保存密码”处选择“否”,或仅使用 API Key;这两种用法无需安装 Chrome、Playwright 或系统凭据服务。
|
|
36
|
+
|
|
37
|
+
本项目尚未发布 npm 包;使用本地目录安装,不要把仓库名称当作已发布的 npm 包名。
|
|
38
|
+
|
|
39
|
+
## 三种接入方式
|
|
40
|
+
|
|
41
|
+
### 仅 API Key(不查询额度)
|
|
42
|
+
|
|
43
|
+
1. 选择「仅 API Key(不查询额度)」。
|
|
44
|
+
2. 填写账号名称和 Coding Plan API Key,保存后即可调用模型,无需网页登录、账号密码或套餐 ID。
|
|
45
|
+
3. 使用插件内置的模型清单;不访问官网模型列表、额度或单点登录接口,也不访问系统凭据库。额度状态为「仅 API Key(未启用额度查询)」,不报告额度错误或虚构剩余额度。
|
|
46
|
+
|
|
47
|
+
此模式不测试 Key 是否有效,不消耗套餐发起试用请求;真实调用由天翼云验证 Key。没有套餐用量数据,magpie 无法依据本账号的三个额度窗口进行预先分流,仍可使用模型自动分组和常规请求失败处理。
|
|
48
|
+
|
|
49
|
+
### 浏览器单点登录(不保存密码,需要图形桌面)
|
|
50
|
+
|
|
51
|
+
1. 填写账号名称、Coding Plan API Key;只有一个有效套餐时,套餐 ID 留空。
|
|
52
|
+
2. 插件打开专用 Chromium 登录窗口,在天翼云官方网站完成登录。可使用官网提供的账号密码、扫码等方式;验证码由用户在官网完成。
|
|
53
|
+
3. 登录成功后,窗口自动关闭,插件保存 Cookie 会话,不保存账号密码。
|
|
54
|
+
4. 助手 Cookie 失效时,优先通过仍有效的 IAM 单点登录会话恢复;IAM 会话也失效时,提示重新选择浏览器登录。
|
|
55
|
+
|
|
56
|
+
专用窗口使用临时浏览器上下文,与日常 Chrome 的登录状态独立,不会读取日常浏览器的密码或 Cookie。已有插件会话的正常续期通过 HTTP 完成,不会定时弹出浏览器。
|
|
57
|
+
|
|
58
|
+
### 账号密码登录
|
|
59
|
+
|
|
60
|
+
1. 填写账号名称、Coding Plan API Key、天翼云账号;只有一个有效套餐时,套餐 ID 留空。
|
|
61
|
+
2. 在输入密码前选择“是否保存密码用于自动重登”:**否,不保存密码** 或 **是,保存到系统凭据库**。
|
|
62
|
+
3. 输入密码后,插件通过官方 HTTP 接口完成登录,确认可以读取模型清单,并保存 Cookie/IAM 会话供模型发现和额度查询使用;无需浏览器。
|
|
63
|
+
|
|
64
|
+
选择“否”时,不读写系统凭据库、不创建密码文件,适用于 headless Linux 和 Docker。密码仅用于本次登录,完成或失败后清空输入引用;IAM 会话也失效后需再次输入密码。若此前同名账号保存过系统密码,旧条目不会被删除,但新的登录会话不再引用它。
|
|
65
|
+
|
|
66
|
+
选择“是”时,账号密码保存在系统凭据库,magpie 登录数据仅保存引用,不保存密码或密码哈希。会话失效时先尝试 IAM 单点登录恢复,无法恢复时使用保存的密码重登。系统凭据库使用 macOS Keychain、Windows Credential Manager,或 Linux Secret Service;Linux 需要可用且已解锁的凭据服务。凭据库不可用时会明确提示,可重新登录并选择“否”;不会静默降级或将密码写到文件。
|
|
67
|
+
|
|
68
|
+
当前 magpie 的原生插件表单采用分步选项,“是否保存”使用是/否选择,未增加自定义页面或复选框。已有登录方式索引保持 0 仅 Key、1 浏览器、2 密码;调用登录接口时,`save_password=yes` 才保存密码,`no` 或未传该值均不保存。已有保存密码的会话继续使用原来的系统凭据库引用。
|
|
69
|
+
|
|
70
|
+
触发图形验证码、短信验证、密码错误或凭据缺失时,自动重登会停止,提示用户重新登录;不会持续尝试错误密码。切换同名账号到浏览器登录后,不再使用保存的密码,并尝试移除旧凭据。如果系统凭据库不可用,浏览器登录仍可完成;可在系统凭据库恢复后用下文命令移除旧密码。
|
|
71
|
+
|
|
72
|
+
### 先填 Key,以后再登录/关闭额度查询
|
|
73
|
+
|
|
74
|
+
无需删除账号。在同一供应商中再次发起登录,选择账号密码登录或浏览器登录,填写**完全相同的账号名称和原 API Key**,完成官网认证即可启用模型发现与额度查询。magpie 会更新原账号,保留原账号槽位和其他账号;登录失败则保留原记录。宿主没有向插件登录表单提供已保存 Key 的读取接口,所以补登录时仍需重新填写 Key。若想新增独立账号,请使用不同名称。
|
|
75
|
+
|
|
76
|
+
已启用额度查询后,也可用相同名称和原 Key 重新选择「仅 API Key」,关闭该账号的网页登录与额度查询。它不会自动删除以前保存在系统凭据库中的密码;需要清除密码时使用下文的凭据移除命令。
|
|
77
|
+
|
|
78
|
+
## API Key 与网页登录的区别
|
|
79
|
+
|
|
80
|
+
三种方式都需要 Coding Plan API Key;网页登录只在启用模型发现和额度查询时需要:
|
|
81
|
+
|
|
82
|
+
| 凭证 | 用途 | 保存位置 |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| Coding Plan API Key | 模型推理调用 | magpie 的插件登录存储 |
|
|
85
|
+
| Cookie/IAM 会话 | 获取官方模型列表、查询套餐额度 | magpie 的插件登录存储 |
|
|
86
|
+
| 账号密码(不保存模式) | 本次官网登录 | 登录完成/失败后不保留 |
|
|
87
|
+
| 账号密码(保存模式) | 会话无法恢复时重新登录 | 系统凭据库 |
|
|
88
|
+
|
|
89
|
+
API Key、Cookie/IAM 会话没有由插件额外加密,应将 magpie 的登录存储及其备份视为敏感文件。请勿提交、同步到公开仓库或作为 Issue 附件上传。密码进入系统凭据库不代表所有登录数据都在钥匙串中;更多说明和漏洞报告方式见 [SECURITY.md](SECURITY.md)。
|
|
90
|
+
|
|
91
|
+
账号名称用于区分多个账号,重新使用同一名称登录会更新该账号。请确保填写的 API Key 属于所登录的账号。插件不自动创建或重置天翼云密钥。
|
|
92
|
+
|
|
93
|
+
magpie 会先询问登录参数,包括 API Key,再开始登录。这遵循宿主提供的登录流程。
|
|
94
|
+
|
|
95
|
+
## 模型和额度
|
|
96
|
+
|
|
97
|
+
接入后,模型直接出现在 `ctyun/<模型ID>` 下,无需设置自定义模型列表或余额查询地址。例如:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
ctyun/deepseek-v4.1-flash
|
|
101
|
+
ctyun/glm-5.3
|
|
102
|
+
ctyun/MiniMax-M2.5
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
已完成网页登录时,模型清单优先从官网获取;仅 API Key 模式使用内置清单。注册的模型 ID 和显示名称统一去除末尾的 `-oc`,保留原有大小写、版本号和其他变体,例如 `MiniMax-M2.5-oc` 注册为 `MiniMax-M2.5`,`deepseek-v4-pro-0813-oc` 注册为 `deepseek-v4-pro-0813`。GLM-5.3 固定显示为 `GLM-5.3`。发送请求时,插件按内置清单或当前账号的官网发现映射恢复准确的官方 ID;不同账号的映射相互隔离,官网原本不带 `-oc` 的模型不添加后缀。
|
|
106
|
+
|
|
107
|
+
magpie 按模型 ID 自动分组,显示名称本身不决定分组。若此前勾选了带 `-oc` 的旧 ID,需在供应商模型列表中改选去掉后缀的新 ID;magpie 会保留旧勾选,仅更新插件清单或重启不会迁移它。直接选择了旧 ID 的客户端、保存的路由组成员也需同步更新。官网暂不可用时保留此前的清单;初次未登录时使用 2026-10-30 的官方清单快照,共 9 个模型。模型能力保守声明为文本输入输出和工具调用,不虚构上下文长度或图片能力。
|
|
108
|
+
|
|
109
|
+
天翼云不公布按 token 计费的单价。为了让 magpie 能估算费用,内置模型按原厂国际站的官方价格(美元/百万 token,2026-10-31 核对)声明 `cost`,见 `src/model-profiles.mjs`。这只是参考价,Coding Plan 实际按套餐额度计费。`deepseek-v4-flash-0731` 已不在 DeepSeek 价格表中,沿用 V4 Flash 首发价;新发现的未知模型不填价格。
|
|
110
|
+
|
|
111
|
+
思考设置按模型声明:[GLM-5.3](https://docs.z.ai/guides/llm/glm-5.3)/[Flash](https://docs.z.ai/guides/vlm/glm-5.3-flash) 支持 `low/high/max`,不能关闭,默认 `max`;[DeepSeek](https://api-docs.deepseek.com/guides/thinking_mode/) 支持 `none/low/high/max`,默认开启、`high`;[GLM-5.1](https://docs.z.ai/guides/llm/glm-5.1)、[Kimi-K2.5](https://huggingface.co/moonshotai/Kimi-K2.5)、[Qwen3.6-Flash](https://help.aliyun.com/en/model-studio/deep-thinking) 提供 `none/enabled` 开关,默认开启;[MiniMax-M2.5](https://platform.minimax.io/docs/api-reference/text-openai-api) 仅 `enabled`。除已经实测的 GLM-5.3-oc 和 DeepSeek-V4.1-Flash,其余先按原厂官方能力配置,天翼云行为待逐一核验。
|
|
112
|
+
|
|
113
|
+
`enabled` 是开启开关,不是分级强度。插件将开关转换为各模型原生的 `thinking.type` 或 `enable_thinking`;Qwen 的数值型 `thinking_budget` 保留,不虚构档位到预算的映射。DeepSeek 显式的 `thinking.type` 优先于 `reasoning_effort`。不传强度时保留原厂默认;新发现的未知模型不自动套用已有模型的规则。
|
|
114
|
+
|
|
115
|
+
插件还会增量修复天翼云 SSE 的空 ID 占位首帧,以及已知工具续片中为空的 `type/id`,解决 SDK 聚合失败。保留正文、推理、工具参数、用量和上游错误,不补造响应 ID 或成功结果。
|
|
116
|
+
|
|
117
|
+
启用网页登录后,magpie 用量页、`magpie quota` 和原生配额接口会显示三个窗口:
|
|
118
|
+
|
|
119
|
+
- `5 hours`:近 5 小时。
|
|
120
|
+
- `Weekly`:本周。
|
|
121
|
+
- `Plan total`:套餐总量。对当前按月套餐而言,是订阅周期总额度,不假设为自然月。
|
|
122
|
+
|
|
123
|
+
官方返回的是使用比例,没有精确剩余请求次数。插件将 `0.008` 转为 magpie 原生配额需要的 `0.8`(百分比)。刷新时间优先使用明确的套餐到期时间,其余根据官方统计时间和“多少分后刷新”的提示估算,精度受官网提示舍入影响。
|
|
124
|
+
|
|
125
|
+
额度属于整个账号套餐,多把 Key 共享,不是每个 Key 独享。多个有效套餐时,登录参数中填写官网对应的套餐 ID,避免错误地合并额度。
|
|
126
|
+
|
|
127
|
+
三个窗口通过 `auth.usage()` 返回,供 magpie 展示并参与配额路由。最终路由行为取决于 magpie 版本和用户的路由配置。网页登录失效会显示额度查询错误,但不会把仍可用的模型 API Key 一并标为失效。
|
|
128
|
+
|
|
129
|
+
模型和额度读取更新的会话仅缓存在当前宿主内存中,不调用 `client.auth.set`。持久化续期通过 magpie 原生 `auth.refresh` 返回补丁,由宿主在写锁内核对原账号后保存,避免查询结束时恢复已删除账号。宿主重启会丢弃尚未持久化的会话变化,必要时从原会话重新进行单点登录/密码恢复。此行为依赖宿主支持原生续期,已验证 `host-57e4026712e7.js`;其他宿主需另行检查。
|
|
130
|
+
|
|
131
|
+
推理请求只携带 API Key,Cookie 和网页登录请求头不会转发到推理接口。插件使用固定的官方域名,拒绝将密钥发送到其他地址。
|
|
132
|
+
|
|
133
|
+
## 开发与验证
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npm test
|
|
137
|
+
npm run check
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
可选的真实浏览器集成测试使用隔离的无头 Chrome,网页请求全部拦截为本地测试响应,不连接天翼云或读取日常浏览器资料:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
npm run test:browser
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
单元测试覆盖仅 API Key 模式不访问官网/凭据库、密码登录、单点登录恢复、验证码中断、并发恢复、多个账号隔离、原生额度转换、浏览器窗口清理,以及推理与网页登录凭据的隔离。
|
|
147
|
+
|
|
148
|
+
账号删除另有实际 magpie Bun 宿主集成回归,使用临时认证文件、两个宿主和假会话,不读取真实账号或连接天翼云:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
bun scripts/host-account-regression.mjs --host /path/to/host-57e4026712e7.js
|
|
152
|
+
bun scripts/host-smoke.mjs --host /path/to/host-57e4026712e7.js
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
回归覆盖仅 API Key 接入、失败补登录保留原记录、补浏览器/密码登录后保留账号键及其他账号、浏览器与凭据库均不可用时的不保存密码登录,以及主键/后缀键删除、成功和失败的额度查询、检查与写入间的竞争、模型快照、原生续期完成后账号仍保持删除。删除问题的复现证据和宿主限制见 [ACCOUNT_DELETION.md](ACCOUNT_DELETION.md)。
|
|
156
|
+
|
|
157
|
+
模型思考参数和流式兼容修复也有离线回归。`scripts/sdk-replay.py` 可用 OpenAI Python SDK 重放既有响应,验证真实 ID 和工具参数聚合,全程使用 MockTransport,不消耗套餐。在独立 Python 虚拟环境安装开发依赖 `openai==2.48.0`、`httpx==0.28.1` 后,运行 `python scripts/sdk-replay.py`。已验证正常文本与工具聚合成功,长度截断及上游错误仍按原样报错。
|
|
158
|
+
|
|
159
|
+
`docs/` 为本地研究资料目录,已从 Git 跟踪和发布清单中排除;日常使用不依赖该目录。
|
|
160
|
+
|
|
161
|
+
显式执行官方接口实测:
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
cp .env.example .env
|
|
165
|
+
# 在本地 .env 中填写测试账号;文件已被 git 忽略。
|
|
166
|
+
npm run test:live -- --env .env
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
实测脚本只登录并读取模型和额度;模拟删除助手 Cookie 后验证 IAM 恢复。不写入系统凭据库,不保存 Cookie,不修改 magpie 配置,也不发起模型推理调用。输出仅含模型 ID、额度和测试状态。
|
|
170
|
+
|
|
171
|
+
### 已验证与限制
|
|
172
|
+
|
|
173
|
+
- 2026-10-30:使用官方接口实测了账号密码登录、9 个模型的发现、三个额度窗口,以及清除助手 Cookie 后从 IAM 会话恢复。该次登录没有要求验证码。
|
|
174
|
+
- 2026-10-31:迁移本机旧 GLM 勾选后,实际 magpie 网关已公布 `GLM-5.3`,自动发现的 `auto/glm-5.3` 同时包含智谱与本插件的 `glm-5.3`。请求转换用模拟网络验证,未为验证路由发起收费推理。
|
|
175
|
+
- 助手 Cookie 的客户端有效期约为 7 天;服务端仍可能提前撤销会话。自动恢复解决会话过期后的重新获取,不保证会话永久有效或始终免验证码。
|
|
176
|
+
- 已通过 magpie 实际 Bun 插件宿主的加载、供应商注册、三种接入方式、保存密码的是/否选择及密码掩码输入检查;测试使用临时空白登录存储,不修改现有 magpie 配置。
|
|
177
|
+
- 已通过真实隔离无头 Chrome 的测试响应登录流程,验证 Cookie 提取、IAM 会话保留和窗口清理。系统凭据库也已用临时测试条目验证写入、读取与删除。实际官网浏览器登录仍需要用户在可见窗口中完成,首次使用时请验证。
|
|
178
|
+
- 浏览器登录需要 magpie 所在主机有可操作的图形桌面。纯远程/无桌面环境可使用不保存密码的登录方式;只有选择「保存到系统凭据库」时才依赖凭据服务。外部凭据文件/Docker Secrets 读取方式尚未实现。
|
|
179
|
+
- 卸载插件或在 magpie 中删除账号不会自动删除系统凭据库条目。可用下面的命令移除本插件保存的密码:
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
npm run credentials:forget -- "账号名称"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
这是第三方插件,依赖官网当前使用的接口。官方调整认证或响应结构时需要更新插件。请按天翼云套餐的适用范围使用。
|
|
186
|
+
|
|
187
|
+
## 项目结构
|
|
188
|
+
|
|
189
|
+
- `index.mjs`:magpie/OpenCode 风格插件入口。
|
|
190
|
+
- `src/plugin.mjs`:API Key 接入、两种网页登录方式、账号会话协调、模型及原生配额钩子。
|
|
191
|
+
- `src/session.mjs`:官方 IAM/CAS 登录、Cookie 容器、模型和额度解析。
|
|
192
|
+
- `src/browser.mjs`:专用浏览器登录与会话提取。
|
|
193
|
+
- `src/credentials.mjs`:系统凭据库读写。
|
|
194
|
+
- `src/model-profiles.mjs`:按精确模型 ID 配置思考档位及原生参数转换。
|
|
195
|
+
- `src/sse.mjs`:流式占位帧和工具续片兼容修复。
|
|
196
|
+
- `scripts/live-check.mjs`:显式的官方接口实测。
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
项目原创代码采用 MIT 许可。官网图标及第三方品牌标识不通过本项目的 MIT 许可重新授权;来源、许可核验状态和权利说明见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。本项目是第三方适配插件,不代表天翼云官方。
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 安全与隐私
|
|
2
|
+
|
|
3
|
+
本项目是第三方 MAGPIE 供应商插件,不代表天翼云官方。当前维护版本为 0.3.0。
|
|
4
|
+
|
|
5
|
+
## 登录数据
|
|
6
|
+
|
|
7
|
+
- 仅 API Key 模式只保存账号名称与推理密钥,不创建网页登录会话、不访问额度或模型发现接口、不读取系统凭据库。后续用相同账号名称和原 Key 完成网页登录,会由宿主更新原记录;失败登录不替换原记录。切换回仅 API Key 不自动移除先前保存的系统密码。
|
|
8
|
+
- 账号密码入口通过天翼云官方 HTTPS 接口登录,密码输入前明确选择是否保存。选择“否”或调用接口时未提供 `save_password`,仅将密码用于本次登录,不读写系统凭据库、不创建密码文件;输入引用在完成或失败后清空。它保存 Cookie/IAM 会话以查询额度,IAM 失效后需手动重登。
|
|
9
|
+
- 保存密码模式将账号密码保存在系统凭据库,凭据库不可用时不回退为明文密码文件。切换到不保存密码模式不会自动删除旧系统密码,新会话不再引用旧条目。
|
|
10
|
+
- 浏览器模式在运行 magpie 的主机上打开可见窗口,使用隔离的临时浏览器上下文,不读取日常 Chrome 的密码或 Cookie,也不保存账号密码。无图形会话的 Linux 环境会提示使用密码登录或仅 Key。
|
|
11
|
+
- Coding Plan API Key、Cookie/IAM 会话和账号标签由 MAGPIE 登录存储保存。插件没有为这些字段额外提供静态加密,不能将其视为系统钥匙串中的数据。应保护宿主登录文件及其备份、同步副本和整机备份。
|
|
12
|
+
- 推理请求仅携带 API Key;插件不向推理接口转发网页登录 Cookie。模型列表和额度查询使用官方网页会话。插件没有自行实现遥测或第三方凭据上报。
|
|
13
|
+
- 在 MAGPIE 中删除账号或卸载插件,不会自动删除系统凭据库中的条目。使用 `npm run credentials:forget -- "账号名称"` 移除保存的密码;宿主中的 API Key 和 Cookie 需在 MAGPIE 中另行清理。
|
|
14
|
+
|
|
15
|
+
## 分享日志和报告
|
|
16
|
+
|
|
17
|
+
请勿上传 `.env`、登录存储、Cookie 容器、密码或密码哈希、API Key、验证码、登录票据、HAR 抓包或完整请求头。模型提示、回复、额度、账号标签、请求 ID 和时间戳也可能涉及隐私,分享前应删除不必要的信息。
|
|
18
|
+
|
|
19
|
+
自动测试使用本地模拟响应;`npm run test:live` 才会显式登录官网并读取模型和额度,其输出可能包含套餐与用量信息。常规测试不需要真实凭据,也不应为公开 CI 配置个人天翼云账号。
|
|
20
|
+
|
|
21
|
+
## 报告漏洞
|
|
22
|
+
|
|
23
|
+
不要在公开 Issue 或讨论中贴出漏洞利用细节、真实凭据或他人数据。若仓库已启用 GitHub 私密漏洞报告,请通过仓库 Security 页面中的私密报告入口联系维护者。入口不可用时,可先提交不含敏感细节的 Issue,请维护者提供私密联系渠道。
|
|
24
|
+
|
|
25
|
+
若凭据已经外泄,应先在官方控制台撤销或更换凭据。仅删除仓库文件、改为私有或清理 Git 历史,不会让已经复制的凭据失效。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# 第三方资源与品牌标识
|
|
2
|
+
|
|
3
|
+
## 天翼云网站图标
|
|
4
|
+
|
|
5
|
+
- 来源:<https://eaichat.ctyun.cn/chat/favicon.ico>,由 <https://eaichat.ctyun.cn/> 的页面指定。
|
|
6
|
+
- 获取日期:2026-10-31。
|
|
7
|
+
- 使用方式:原始 ICO 文件以 Base64 形式嵌入 `package.json` 的 `magpie.icon`,用于标识适配的服务,未修改图像内容。
|
|
8
|
+
- 许可核验状态:目前未随该图标取得独立的再分发许可文件。注明来源不等同于已取得授权,公开分发前仍需确认权利方允许的使用方式。
|
|
9
|
+
|
|
10
|
+
本项目不主张拥有该图标或相关品牌标识的权利,也不通过本项目的 MIT 许可对它们重新授权。项目原创代码的 MIT 许可与第三方图标、商标的使用权限应分别理解。本插件为非官方适配项目,不表示与天翼云存在官方合作或背书。
|
|
11
|
+
|
|
12
|
+
## 软件依赖
|
|
13
|
+
|
|
14
|
+
本项目使用的 `tough-cookie`、`playwright`、`@napi-rs/keyring` 及其传递依赖,分别适用各包附带的许可证。依赖版本和来源记录在 `package-lock.json`;本项目的 MIT 许可不替代它们各自的许可证。
|
package/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CtyunCodingPlanPlugin } from './src/plugin.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magpie-ctyun",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "天翼云息壤「ctyun」Coding Plan 的 MAGPIE 供应商插件,支持仅 API Key 接入、可选网页登录、模型发现与原生额度窗口。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.mjs",
|
|
7
|
+
"exports": "./index.mjs",
|
|
8
|
+
"files": [
|
|
9
|
+
"index.mjs",
|
|
10
|
+
"src/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"CHANGELOG.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"SECURITY.md",
|
|
15
|
+
"ACCOUNT_DELETION.md",
|
|
16
|
+
"THIRD_PARTY_NOTICES.md",
|
|
17
|
+
"scripts/forget-credentials.mjs"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test test/*.test.mjs",
|
|
21
|
+
"check": "node --check index.mjs && node --check src/session.mjs && node --check src/browser.mjs && node --check src/plugin.mjs && node --check src/model-profiles.mjs && node --check src/sse.mjs",
|
|
22
|
+
"test:live": "node scripts/live-check.mjs",
|
|
23
|
+
"browser:install": "playwright install chromium",
|
|
24
|
+
"credentials:forget": "node scripts/forget-credentials.mjs",
|
|
25
|
+
"test:browser": "node scripts/browser-smoke.mjs"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=22"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"magpie",
|
|
32
|
+
"opencode-plugin",
|
|
33
|
+
"ctyun",
|
|
34
|
+
"coding-plan"
|
|
35
|
+
],
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/sheauhuu/magpie-ctyun-coding-plan.git",
|
|
39
|
+
"directory": "."
|
|
40
|
+
},
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"tough-cookie": "^6.0.2"
|
|
44
|
+
},
|
|
45
|
+
"optionalDependencies": {
|
|
46
|
+
"@napi-rs/keyring": "^2.1.2",
|
|
47
|
+
"playwright": "^1.64.1"
|
|
48
|
+
},
|
|
49
|
+
"magpie": {
|
|
50
|
+
"maxConcurrency": 4,
|
|
51
|
+
"icon": "data:image/x-icon;base64,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",
|
|
52
|
+
"name": "天翼云息壤「ctyun」",
|
|
53
|
+
"summary": {
|
|
54
|
+
"en": "Unofficial 天翼云息壤「ctyun」Coding Plan provider for magpie: API key sign-in, optional browser/password login, official model discovery and native quota windows.",
|
|
55
|
+
"zh": "非官方天翼云息壤「ctyun」Coding Plan 接入:支持仅 API Key 调用模型,可选密码登录或浏览器单点登录以获取官方模型发现与 5 小时/周/套餐总量三个额度窗口。"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CredentialStore, credentialId } from '../src/credentials.mjs';
|
|
2
|
+
const account = process.argv.slice(2).join(' ').trim();
|
|
3
|
+
if (!account) { console.error('请提供登录时使用的账号名称'); process.exitCode = 1; }
|
|
4
|
+
else {
|
|
5
|
+
try {
|
|
6
|
+
const removed = await new CredentialStore().remove(credentialId(account));
|
|
7
|
+
console.log(removed ? '已移除该账号在本插件系统凭据库中的条目' : '该账号没有已保存的密码条目');
|
|
8
|
+
} catch (error) { console.error(error.message); process.exitCode = 1; }
|
|
9
|
+
}
|
package/src/browser.mjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Cookie } from 'tough-cookie';
|
|
2
|
+
import { APP, ENTRY, CtyunError, CtyunSession } from './session.mjs';
|
|
3
|
+
|
|
4
|
+
export async function importBrowserCookies(session, cookies) {
|
|
5
|
+
for (const c of cookies) {
|
|
6
|
+
const domain = c.domain.replace(/^\./, '');
|
|
7
|
+
if (!(domain === 'ctyun.cn' || domain.endsWith('.ctyun.cn'))) continue;
|
|
8
|
+
const cookie = new Cookie({ key: c.name, value: c.value, domain: c.domain.replace(/^\./, ''),
|
|
9
|
+
path: c.path || '/', secure: c.secure, httpOnly: c.httpOnly,
|
|
10
|
+
hostOnly: !c.domain.startsWith('.'), sameSite: c.sameSite?.toLowerCase(),
|
|
11
|
+
...(c.expires > 0 ? { expires: new Date(c.expires * 1000) } : {}) });
|
|
12
|
+
await session.jar.setCookie(cookie, `${c.secure ? 'https' : 'http'}://${domain}${c.path || '/'}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// A fresh, dedicated browser context: no password manager data is imported or saved.
|
|
17
|
+
// It is closed in finally, including timeout and manual window-close paths.
|
|
18
|
+
export async function browserLogin({ state, deviceCode, chromium, timeout = 300_000, sessionFactory,
|
|
19
|
+
platform = process.platform, env = process.env } = {}) {
|
|
20
|
+
// The default driver opens a visible window on the magpie host, not in
|
|
21
|
+
// the browser viewing its web UI. Injected drivers may supply their own display.
|
|
22
|
+
if (!chromium && platform === 'linux' && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
|
|
23
|
+
throw new CtyunError('浏览器登录需要 magpie 所在主机具备可操作的图形桌面;当前为无桌面 Linux 环境。请使用“账号密码登录”并选择不保存密码,或选择“仅 API Key(不查询额度)”。', 'BROWSER_HEADLESS');
|
|
24
|
+
}
|
|
25
|
+
if (!chromium) {
|
|
26
|
+
try { ({ chromium } = await import('playwright')); }
|
|
27
|
+
catch { throw new CtyunError('浏览器登录组件未安装,请在插件目录运行 npm install 和 npm run browser:install', 'BROWSER'); }
|
|
28
|
+
}
|
|
29
|
+
let browser;
|
|
30
|
+
try {
|
|
31
|
+
// Prefer an installed Chrome binary, still with an isolated temporary
|
|
32
|
+
// profile. Fall back to Playwright's Chromium when Chrome is unavailable.
|
|
33
|
+
for (const options of [{ channel: 'chrome', headless: false }, { headless: false }]) {
|
|
34
|
+
try { browser = await chromium.launch(options); break; } catch { /* Try the next installed browser. */ }
|
|
35
|
+
}
|
|
36
|
+
if (!browser) throw new CtyunError('无法启动专用登录窗口。桌面环境请安装 Chrome 或在插件目录运行 npm run browser:install;无桌面服务器或 Docker 请使用“账号密码登录”并选择不保存密码。', 'BROWSER');
|
|
37
|
+
const context = await browser.newContext();
|
|
38
|
+
const initial = sessionFactory?.() || new CtyunSession({ state, deviceCode });
|
|
39
|
+
const saved = initial.jar.serializeSync().cookies;
|
|
40
|
+
if (saved.length) await context.addCookies(saved.map(c => ({ name: c.key, value: c.value,
|
|
41
|
+
domain: c.hostOnly ? c.domain : `.${c.domain.replace(/^\./, '')}`, path: c.path || '/',
|
|
42
|
+
secure: !!c.secure, httpOnly: !!c.httpOnly,
|
|
43
|
+
...(c.expires && c.expires !== 'Infinity' ? { expires: Date.parse(c.expires) / 1000 } : {}) })));
|
|
44
|
+
const page = await context.newPage();
|
|
45
|
+
await page.goto(ENTRY, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
46
|
+
const deadline = Date.now() + timeout;
|
|
47
|
+
while (Date.now() < deadline) {
|
|
48
|
+
if (!browser.isConnected() || page.isClosed()) throw new CtyunError('登录窗口已关闭', 'CANCELLED');
|
|
49
|
+
if (new URL(page.url()).hostname === new URL(APP).hostname) {
|
|
50
|
+
const session = sessionFactory?.() || new CtyunSession({ deviceCode: initial.deviceCode });
|
|
51
|
+
await importBrowserCookies(session, await context.cookies());
|
|
52
|
+
try {
|
|
53
|
+
await session.models();
|
|
54
|
+
return session;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.code !== 'AUTH') throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, 750));
|
|
60
|
+
}
|
|
61
|
+
throw new CtyunError('登录等待超时,请重新打开登录窗口', 'TIMEOUT');
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error instanceof CtyunError) throw error;
|
|
64
|
+
throw new CtyunError('浏览器登录未完成,请重新尝试', 'BROWSER');
|
|
65
|
+
} finally { await browser?.close().catch(() => {}); }
|
|
66
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { CtyunError } from './session.mjs';
|
|
3
|
+
|
|
4
|
+
const SERVICE = 'ctyun';
|
|
5
|
+
export function credentialId(account) { return createHash('sha256').update(account).digest('hex'); }
|
|
6
|
+
|
|
7
|
+
export class CredentialStore {
|
|
8
|
+
async entry(id) {
|
|
9
|
+
try {
|
|
10
|
+
const { Entry } = await import('@napi-rs/keyring');
|
|
11
|
+
return new Entry(SERVICE, id, process.platform === 'linux' ? { linux: { store: 'secret-service' } } : undefined);
|
|
12
|
+
} catch { throw new CtyunError('无法使用系统凭据库;请启用钥匙串/凭据管理器,或在“账号密码登录”中选择不保存密码', 'KEYRING'); }
|
|
13
|
+
}
|
|
14
|
+
async set(id, credentials) {
|
|
15
|
+
try { (await this.entry(id)).setPassword(JSON.stringify(credentials)); }
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (error instanceof CtyunError) throw error;
|
|
18
|
+
throw new CtyunError('系统凭据库拒绝保存账号密码,请在“账号密码登录”中选择不保存密码', 'KEYRING');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async get(id) {
|
|
22
|
+
try {
|
|
23
|
+
const value = (await this.entry(id)).getPassword();
|
|
24
|
+
return value ? JSON.parse(value) : null;
|
|
25
|
+
} catch { return null; }
|
|
26
|
+
}
|
|
27
|
+
async remove(id, { optional = false } = {}) {
|
|
28
|
+
let entry;
|
|
29
|
+
try { entry = await this.entry(id); }
|
|
30
|
+
catch (error) { if (optional) return false; throw error; }
|
|
31
|
+
try {
|
|
32
|
+
if (entry.getPassword() === null) return false;
|
|
33
|
+
return entry.deletePassword();
|
|
34
|
+
} catch {
|
|
35
|
+
if (optional) return false;
|
|
36
|
+
throw new CtyunError('系统凭据库未能移除旧密码,请在系统凭据管理器中处理后重试', 'KEYRING');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|