opencode-lobster-auth 0.1.1 → 0.2.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/README.zh-CN.md CHANGED
@@ -1,151 +1,211 @@
1
- # opencode-lobster-auth
2
-
3
- 为 [magpie](https://usemagpie.ai) 提供 provider `lobster`:登录
4
- [LobsterAI](https://lobsterai.youdao.com)(有道龙虾,网易有道的桌面 agent)
5
- 账号,把它名下的模型接进来。约 30 个模型,含 DeepSeek、GLM、Kimi、Qwen、
6
- MiniMax、豆包。
7
-
8
- [English](README.md) · **中文**
9
-
10
- ## 安装
11
-
12
- 这是一个文件夹插件,没有发布到 npm:
13
-
14
- ```sh
15
- magpie plugin add /path/to/magpie-lobster
16
- magpie plugin login lobster
17
- ```
18
-
19
- ## 登录
20
-
21
- 两种方式,任选其一。两者都用有道账号标识(`yid`)给账号命名,所以对一个
22
- 已经用第一种方式登录过的账号再用第二种登录,是原地替换。
23
-
24
- - **浏览器登录(有道账号)。** 插件在本机 `127.0.0.1` 起一个回调服务,
25
- magpie 打开有道登录页。浏览器带着授权码跳回来,插件拿它在
26
- `/api/auth/exchange` 换取 token。存成一个 `oauth` 账号,带 access 与
27
- refresh token 及到期时间,因此 magpie 会为它续期。
28
- - **从桌面端导入。** 使用 LobsterAI 桌面端当前登录的账号。**不打开任何页面,
29
- 不需要输入任何东西。** 只读桌面端的 SQLite 库,绝不写入:
30
- - Windows:`%APPDATA%\LobsterAI\lobsterai.sqlite`
31
- - macOS:`~/Library/Application Support/LobsterAI/lobsterai.sqlite`
32
- - Linux:`~/.config/LobsterAI/lobsterai.sqlite`
33
-
34
- 库不存在,或者被桌面端加密存放,这条路会带着说明失败,改用浏览器登录即可。
35
-
36
- 两种方式都保存在 magpie 的 `plugin-auth.json`。
37
-
38
- ### 为什么导入这条路不保存 token
39
-
40
- 导入方式**一个字节的凭据都不存**(`access` 和 `refresh` 都是空串,`expires`
41
- 为 0),只在每次请求时现读桌面端的库:
42
-
43
- - 抄一份 `refreshToken` 会与桌面端共用同一个凭据:**谁先续期谁就把对方踢
44
- 下线**。留空则双方互不干扰。
45
- - `expires` 为 0,magpie 因此永不续期(它只对设了到期时间的 `oauth` 账号做
46
- 续期),桌面端的会话不会被搅动。
47
- - 现读意味着桌面端续期之后,插件下一次请求自动用上新 token,不必重新登录。
48
-
49
- 代价是:桌面端退出登录、卸载,或者库变得不可读,这个账号会**立刻**失效,
50
- 并被标记为需要重新登录。这比用一个已经悄悄失效的旧 token 更诚实。
51
-
52
- ### 续期
53
-
54
- 浏览器登录的账号由 magpie 通过插件的 `auth.refresh` 在到期前 6 小时续期。
55
- 导入的账号如上一节所述,结构性不会被续期。
56
-
57
- 插件从不调用 LobsterAI 自己的续期接口去验证或刷新任何东西:
58
- `/api/auth/refresh` 可能消耗账号真实的 refresh token,因此刻意不碰它。
59
-
60
- ## 请求
61
-
62
- 对话走 `https://lobsterai-server.youdao.com/api/proxy/v1` 的 chat
63
- completions。LobsterAI 有三个习惯被处理掉了:
64
-
65
- - **所有回复都是 SSE**,即使请求里写了 `stream: false`。非流式调用方会拿到
66
- 由它重组出的一个完整 `chat.completion`;流式调用方拿到的就是原样的事件。
67
- - **错误藏在 200 里。** LobsterAI 把错误放进流里(`event: error`),状态码
68
- 仍是 200 —— 这样调用方没有可故障切换的依据。插件读首条记录,还原成它真正
69
- 表示的状态:`40100` → 401,`40300` → 403,`42900` 或限流措辞 → 429,其余
70
- → 400。被 LobsterAI 拒绝的登录是真 401,原样透传并带上 magpie 的失效标记,
71
- 账号因此会被标出来。
72
- - **请求带上桌面端的标识头**(`X-LobsterAI-Client-Version`、
73
- `X-LobsterAI-Client-Capabilities`)。LobsterAI 不强制要求,但它们标明了
74
- 插件所说的特性集。
75
-
76
- ### 思考档位
77
-
78
- 模型的 `thinkingConfig` 决定它有哪些档位 —— 当前目录里是 `off`、`high`、
79
- `max` —— 它们通过 LobsterAI 自己的字段上线:
80
- `{"lobsterai_options":{"version":1,"thinking":{"level":"off"}}}`。
81
-
82
- 有两件事值得知道:
83
-
84
- - **`lobsterai_options` 只发给自带档位表的模型。** 29 个模型里有 21 个没有
85
- 档位表,给这些模型带上该字段是硬错误(`code 4000`,*model does not have a
86
- valid thinkingConfig*),整轮对话直接失败。只有在有档位表可映射时,档位才会
87
- 被传下去。
88
- - **最低档对外叫 `none`,不叫 `off`。** magpie 不使用 variant 里存的东西,它
89
- 按自己的一条阶梯(`none/minimal/low/medium/high/xhigh/max`)把
90
- `reasoning_effort` 映射到「模型声明过的那个档位名」。`off` 不在那条阶梯上,
91
- 所以声明成 `off` 的那一档永远选不中。插件对外声明 `none`,写给 LobsterAI
92
- 时再换回 `off`。
93
-
94
- 在 `deepseek-flash` 上实测:`none` 会让 `reasoning_content` 恰好为 0 字节,
95
- 而 `high`、`max` 和不指定档位都不会。
96
-
97
- ### Kimi K3
98
-
99
- LobsterAI 的 `kimi-k3` 采样参数由服务端固定,因此 `temperature`、`top_p`、
100
- `n` 和两个 penalty 会被删掉,`reasoning_effort` 固定为 `max`,缺少
101
- `reasoning_content` 的 assistant 工具调用会被补一个空串。
102
-
103
- `kimi-k3` 属于 LobsterAI「能用但没列出」的模型:`/api/models/available`
104
- 返回 29 个模型,其中没有 K3,但对 `kimi-k3` 发请求能正常作答。它保留在
105
- 本插件为未登录账号声明的兜底表里,所以 `lobster/kimi-k3` 是可选的 ——
106
- 上面那段处理也因此是**实际生效的**,不是预防性代码。
107
-
108
- ## 模型
109
-
110
- 登录后,列表来自账号自己(`/api/models/available`):29 个模型,各带上下文
111
- 窗口、是否收图片、以及它的思考档位。登录之前用一份 5 个模型的静态列表兜底,
112
- 这样 provider 在登录前就可见。
113
-
114
- `costMultiplier` 成为 magpie 的 `rate`。它是**分时计价**:`deepseek-flash`
115
- 在 LobsterAI 的空闲时段是 0.05,高峰时段是 0.1(高峰为北京时间
116
- 09:00–12:00 与 14:00–18:00)。倍率是在拉列表时读的,所以可能有一阵子是过时
117
- 的。
118
-
119
- ## 用量
120
-
121
- `magpie quota` 显示免费额度、它的到期时间和剩余点数,数据来自
122
- `/api/user/quota` 与 `/api/user/profile-summary`。插件上报的是 `kept`:它自己
123
- 不续期,因此不该声称做过一次它没做的续期。
124
-
125
- ## 不做的事
126
-
127
- - **企业账号。** LobsterAI 让它们走另一个页面(`EnterpriseIdentitySelect`)。
128
- 目前只处理个人账号。
129
- - **经过验证的 `/api/auth/refresh`。** 调用它可能消耗账号真实的 refresh
130
- token,因此没有实测。续期这条路是照着桌面端自己的代码写的,没有与服务端
131
- 核对过。
132
- - **精确的输出上限。** 目录里没有这个字段。插件声明了保守的 32,000;实测
133
- LobsterAI 接受 262,144,在 524,288 时返回 500,所以真实上限在两者之间。
134
- - **OpenCode。** 这个插件是按 OpenCode 的 provider 插件格式写的 —— magpie
135
- 加载的正是这个格式 —— 但只在 magpie 上实测过。`auth.refresh`、
136
- `auth.refreshLead` 以及 `package.json` 里的 `magpie` 字段是 magpie 自己的,
137
- OpenCode 会忽略它们。
138
- - **同时使用多个账号。** magpie 每个 provider 只保留一份登录。
139
-
140
- ## 开发
141
-
142
- ```sh
143
- bun test
144
- ```
145
-
146
- 入口只负责装配钩子。逻辑都在 `lib/` 下,是普通函数,通过 `_internal` 暴露
147
- 出来 —— 因为 magpie 和 OpenCode 都会把每一个导出的函数当成一个独立插件。
148
-
149
- ## 许可
150
-
151
- MIT
1
+ # opencode-lobster-auth
2
+
3
+ 为 [magpie](https://usemagpie.ai) 提供 provider `lobster`:登录
4
+ [LobsterAI](https://lobsterai.youdao.com)(有道龙虾,网易有道的桌面 agent)
5
+ 账号,把它名下的模型接进来。约 30 个模型,含 DeepSeek、GLM、Kimi、Qwen、
6
+ MiniMax、豆包。
7
+
8
+ [English](README.md) · **中文**
9
+
10
+ ## 安装
11
+
12
+ 从 npm 安装:
13
+
14
+ ```sh
15
+ magpie plugin add opencode-lobster-auth
16
+ magpie plugin login lobster
17
+ ```
18
+
19
+ 之后用 `magpie plugin update` 升级。
20
+
21
+ 想跑本地 checkout 而不是已发布的包,就把路径指过去:
22
+
23
+ ```sh
24
+ magpie plugin add /path/to/magpie-lobster
25
+ ```
26
+
27
+ ## 登录
28
+
29
+ 两种方式,任选其一。两者都用有道账号标识(`yid`)给账号命名,所以对一个
30
+ 已经用第一种方式登录过的账号再用第二种登录,是原地替换。
31
+
32
+ - **浏览器登录(有道账号)。** 插件在本机 `127.0.0.1` 起一个回调服务,
33
+ magpie 打开有道登录页。浏览器带着授权码跳回来,插件拿它在
34
+ `/api/auth/exchange` 换取 token。存成一个 `oauth` 账号,带 access 与
35
+ refresh token 及到期时间,因此 magpie 会为它续期。
36
+ - **远程浏览器登录。** 给 magpie 跑在服务器上、Docker 里,或者浏览器在另一台
37
+ 机器上的情况:回调落在浏览器自己的 `127.0.0.1`,到不了 magpie。登录页的普通
38
+ query 里写明了回调端口,因此 magpie 会给出「回调 URL」输入框:登录后复制浏览器
39
+ 停在的那个完整地址,粘贴进去,magpie 会在本机把它送到插件监听的端口。其余与
40
+ 浏览器登录完全一样。
41
+ - **从桌面端导入。** 使用 LobsterAI 桌面端当前登录的账号。**不打开任何页面,
42
+ 不需要输入任何东西。** 只读桌面端的 SQLite 库,绝不写入:
43
+ - Windows:`%APPDATA%\LobsterAI\lobsterai.sqlite`
44
+ - macOS:`~/Library/Application Support/LobsterAI/lobsterai.sqlite`
45
+ - Linux:`~/.config/LobsterAI/lobsterai.sqlite`
46
+
47
+ 库不存在,或者被桌面端加密存放,这条路会带着说明失败,改用浏览器登录即可。
48
+ 导入身份会与当前 token 交叉核对;身份缺失或冲突时会拒绝使用,避免误用别的账号。
49
+
50
+ 两种方式都保存在 magpie 的 `plugin-auth.json`。
51
+
52
+ ### 为什么导入这条路不保存 token
53
+
54
+ 导入方式**一个字节的凭据都不存**(`access` 和 `refresh` 都是空串,`expires`
55
+ 为 0),只在每次请求时现读桌面端的库:
56
+
57
+ - 抄一份 `refreshToken` 会与桌面端共用同一个凭据:**谁先续期谁就把对方踢
58
+ 下线**。留空则双方互不干扰。
59
+ - `expires` 为 0,magpie 因此永不续期(它只对设了到期时间的 `oauth` 账号做
60
+ 续期),桌面端的会话不会被搅动。
61
+ - 现读意味着桌面端续期之后,插件下一次请求自动用上新 token,不必重新登录。
62
+
63
+ 代价是:桌面端退出登录、卸载,或者库变得不可读,这个账号会**立刻**失效,
64
+ 并被标记为需要重新登录。这比用一个已经悄悄失效的旧 token 更诚实。
65
+
66
+ ### 续期
67
+
68
+ 浏览器登录的账号由 magpie 通过插件的 `auth.refresh` 在到期前 6 小时续期。
69
+ 导入的账号如上一节所述,结构性不会被续期。
70
+
71
+ 插件从不调用 LobsterAI 自己的续期接口去验证或刷新任何东西:
72
+ `/api/auth/refresh` 可能消耗账号真实的 refresh token,因此刻意不碰它。
73
+
74
+ ## 请求
75
+
76
+ 对话走 `https://lobsterai-server.youdao.com/api/proxy/v1` 的 chat
77
+ completions。LobsterAI 有三个习惯被处理掉了:
78
+
79
+ - **所有回复都是 SSE**,即使请求里写了 `stream: false`。非流式调用方会拿到
80
+ 由它重组出的一个完整 `chat.completion`;流式调用方拿到的就是原样的事件。
81
+ - **错误藏在 200 里。** LobsterAI 把错误放进流里(`event: error` 或 JSON 错误帧),
82
+ 状态码仍是 200 —— 这样调用方没有可故障切换的依据。插件检查开头的帧并保留
83
+ 纯文本错误;非流式请求还会捕获首个回复分片之后出现的错误。它会还原成真正的
84
+ 状态:`40100` → 401,`40300` → 403,`42900` 或限流措辞 → 429,其余 → 400。
85
+ 流式请求开始输出后无法再改 HTTP 状态,因此中途错误事件会原样保留。被 LobsterAI
86
+ 拒绝的登录是真 401,带上 magpie 的失效标记,账号因此会被标出来。
87
+ - **请求带上桌面端的标识头**(`X-LobsterAI-Client-Version`、
88
+ `X-LobsterAI-Client-Capabilities`)。LobsterAI 不强制要求,但它们标明了
89
+ 插件所说的特性集。
90
+
91
+ ### 思考档位
92
+
93
+ 目录里的模型分两种,插件用两种方式对待。
94
+
95
+ **8 个模型自带 `thinkingConfig`**(`deepseek-*`、`glm-*`),档位是
96
+ `off`/`high`/`max`。它们走 LobsterAI 自己的字段:
97
+ `{"lobsterai_options":{"version":1,"thinking":{"level":"off"}}}`。把该字段发给
98
+ **没有**档位表的模型是硬错误(`code 4000`,*model does not have a valid
99
+ thinkingConfig*),所以只在有档位表可映射时才写。有档位表时只声明当前 profile
100
+ 提供的档位;单独实测的 `reasoning_effort` 档位只用于尚无 profile 的模型。
101
+
102
+ **10 个模型没有档位表**,但同样报 `supportsThinking: true`。对这些模型,插件
103
+ 声明档位、让 magpie 的 `reasoning_effort` 原样透传,LobsterAI 认这个字段。
104
+
105
+ 有两件事值得知道:
106
+
107
+ - **最低档对外叫 `none`,不叫 `off`。** magpie 不使用 variant 里存的东西,它
108
+ 按自己的一条阶梯(`none/minimal/low/medium/high/xhigh/max`)把
109
+ `reasoning_effort` 映射到「模型声明过的那个档位名」。`off` 不在那条阶梯上,
110
+ 所以声明成 `off` 的那一档永远选不中。有档位表时插件声明 `none`、写给
111
+ LobsterAI 时换回 `off`;没有档位表时直接发 `none`,LobsterAI 就认它。
112
+ - **每个模型的档位不一样,是实测出来的,不是假设的。** LobsterAI 会直接拒收
113
+ 一部分:`MiniMax-M3.1-Flash` 拒收 `none`(*requires adaptive thinking*),
114
+ `qwen3.7-max` 只到 `xhigh`,`qwen3.7-plus` 的 `max` 偶发 500。`MiniMax-M3`
115
+ 的思考写在 content 里的 `<think>` 标签中、`reasoning_content` 恒为空,上游
116
+ 也不分档,所以只给 `none` 和 `max`。没实测过的模型一律不声明档位,magpie 会
117
+ 显示成 *thinks, no levels*。
118
+
119
+ 在 `deepseek-flash` 上实测:`none` 会让 `reasoning_content` 恰好为 0 字节,
120
+ 而 `high`、`max` 和不指定档位都不会。在 `kimi-k3.1-pro` 上实测:`none` 为
121
+ 0 字节,`xhigh` 有输出。
122
+
123
+ ### Kimi
124
+
125
+ 所有 Kimi 模型的采样参数都由服务端固定:`temperature` 必须是 1、`top_p` 是
126
+ 0.95、两个 penalty 是 0。所以插件对整个家族删掉 `temperature`、`top_p`、
127
+ `n` 和两个 penalty —— `kimi-k3`、`kimi-k3.1-pro`、`kimi-k2.8-preview` 三个
128
+ 都实测过。客户端只要带了非 1 的 `temperature`(多数 agent 都带),否则会被
129
+ 回一句 `50204 invalid temperature: only 1 is allowed`。
130
+
131
+ 只有 `kimi-k3` 本身将 `reasoning_effort` 固定为 `max`;这个规则**没有**扩展到
132
+ `kimi-k3.1-pro`:后者实测认完整档位,强制 max 会把每一档都悄悄覆盖掉。
133
+ 工具历史补齐是另一条独立规则:保留对 `kimi-k3` 前缀家族(含 `kimi-k3.1-pro`)
134
+ 的兼容处理,缺少 `reasoning_content` 的 assistant 工具调用补空串,但不覆盖已有
135
+ 思考内容,也不改变用户选择的档位。
136
+
137
+ `kimi-k3` 属于 LobsterAI「能用但某些版本目录里没列出」的模型。它保留在本插件
138
+ 为未登录账号声明的兜底表里,所以 `lobster/kimi-k3` 仍可选。
139
+
140
+ ## 模型
141
+
142
+ 登录后,列表来自账号自己(`/api/models/available`)—— 18 或 19 个可用模型,
143
+ 各带上下文窗口、是否收图片、以及它的思考档位。登录之前用一份 5 个模型的静态
144
+ 列表兜底,这样 provider 在登录前就可见。
145
+
146
+ `costMultiplier` 成为 magpie 的 `rate`。它是**分时计价**:`deepseek-flash`
147
+ 在 LobsterAI 的空闲时段是 0.05,高峰时段是 0.1(高峰为北京时间
148
+ 09:00–12:00 与 14:00–18:00)。倍率是在拉列表时读的,所以可能有一阵子是过时
149
+ 的。
150
+
151
+ 输出上限是 131,072。目录里的 `maxTokens` 字段恒为 null,所以这是实测的:
152
+ 18 个模型全部接受 `max_tokens=131072`,`glm-5.3` 自己报的范围就是
153
+ `[1, 131072]`,要到 524,288 才开始 500。
154
+
155
+ ## 用量
156
+
157
+ `magpie quota` 显示剩余点数与最早到期的那批积分,数据来自
158
+ `/api/user/quota` 与 `/api/user/profile-summary`。积分按**钱包窗口**上报
159
+ (`余额·点数`,带 `runsOut`/`runsOutAt`),而不是一个字符串 —— 这样 magpie
160
+ 能显示出到期时间,路由也能优先用快过期的账号。每天签到领的 100 点各是一批、
161
+ 30 天后到期、先到先用,所以知道日期是有意义的。插件上报的是 `kept`:它自己
162
+ 不续期,因此不该声称做过一次它没做的续期。
163
+
164
+ 已失效的免费额度不再作为有效窗口显示 —— 免费期 2026 年 8 月就结束了,
165
+ 之前还按「6% used」显示,看起来像还有一笔可用额度。
166
+
167
+ ## 每日签到
168
+
169
+ 插件实现了 `auth.checkin`,所以 magpie 可以替你领 LobsterAI 的每日积分礼
170
+ (100 点)。**默认关闭**:在 Settings → Usage → Plugins 打开。打开后,每个
171
+ 已登录的 LobsterAI 账号每天(北京时间)各签一次。
172
+
173
+ 这是不开 LobsterAI 桌面端时唯一能领到这些积分的办法 —— 签到入口在桌面端侧栏。
174
+ 领取是幂等的:当天已领会回 `51104`,插件报 `done` 而不是重复领;每次领取都会
175
+ 重新读一次状态复核,通过了才报 `claimed`。HTTP 故障、损坏的业务响应或无效
176
+ 状态统一报 `failed`,不冒充“无活动”或“无资格”,让 magpie 可以当天稍后重试。
177
+
178
+ ## 不做的事
179
+
180
+ - **企业账号。** LobsterAI 让它们走另一个页面(`EnterpriseIdentitySelect`)。
181
+ 目前只处理个人账号。
182
+ - **经过验证的 `/api/auth/refresh`。** 调用它可能消耗账号真实的 refresh
183
+ token,因此没有实测。续期这条路是照着桌面端自己的代码写的,没有与服务端
184
+ 核对过。
185
+ - **OpenCode。** 这个插件是按 OpenCode 的 provider 插件格式写的 —— magpie
186
+ 加载的正是这个格式 —— 但只在 magpie 上实测过。`auth.refresh`、
187
+ `auth.refreshLead`、`auth.checkin` 以及 `package.json` 里的 `magpie` 字段
188
+ 是 magpie 自己的,OpenCode 会忽略它们。
189
+
190
+ ## 多个账号
191
+
192
+ magpie 允许一个 provider 挂多个登录,本插件支持这一点:用浏览器再登一个有道
193
+ 账号,两个都会列出来。
194
+
195
+ 桌面端导入是需要注意的那个。LobsterAI 桌面端同一时间只有一个账号登录,而插件
196
+ 每次请求都现读它的 token。所以桌面导入的账号绑在「导入那一刻登录的账号」上:
197
+ 把桌面端切到别的账号后,原账号会被标成 *需要重新登录*,而不是悄悄花掉另一个
198
+ 账号的积分。重新登录它,或者对需要独立存在的账号改用浏览器登录。
199
+
200
+ ## 开发
201
+
202
+ ```sh
203
+ bun test
204
+ ```
205
+
206
+ 入口只负责装配钩子。逻辑都在 `lib/` 下,是普通函数,通过 `_internal` 暴露
207
+ 出来 —— 因为 magpie 和 OpenCode 都会把每一个导出的函数当成一个独立插件。
208
+
209
+ ## 许可
210
+
211
+ MIT