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.md +255 -175
- package/README.zh-CN.md +211 -151
- package/index.mjs +226 -191
- package/lib/assemble.mjs +7 -1
- package/lib/auth-import.mjs +70 -5
- package/lib/auth-login.mjs +154 -131
- package/lib/checkin.mjs +240 -0
- package/lib/constants.mjs +67 -41
- package/lib/jwt.mjs +34 -34
- package/lib/models.mjs +11 -5
- package/lib/proxy.mjs +51 -19
- package/lib/sse.mjs +63 -67
- package/lib/stream-error.mjs +34 -0
- package/lib/thinking.mjs +58 -12
- package/lib/usage.mjs +46 -4
- package/package.json +38 -37
package/README.md
CHANGED
|
@@ -1,175 +1,255 @@
|
|
|
1
|
-
# opencode-lobster-auth
|
|
2
|
-
|
|
3
|
-
Signs in to a [LobsterAI](https://lobsterai.youdao.com) (有道龙虾, NetEase
|
|
4
|
-
Youdao's desktop agent) account and serves its models as the provider
|
|
5
|
-
`lobster` in [magpie](https://usemagpie.ai). About 30 models: DeepSeek, GLM,
|
|
6
|
-
Kimi, Qwen, MiniMax and Doubao.
|
|
7
|
-
|
|
8
|
-
**English** · [中文](README.zh-CN.md)
|
|
9
|
-
|
|
10
|
-
## Install
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
magpie plugin add
|
|
16
|
-
magpie plugin login lobster
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
The desktop
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
`
|
|
136
|
-
`
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
1
|
+
# opencode-lobster-auth
|
|
2
|
+
|
|
3
|
+
Signs in to a [LobsterAI](https://lobsterai.youdao.com) (有道龙虾, NetEase
|
|
4
|
+
Youdao's desktop agent) account and serves its models as the provider
|
|
5
|
+
`lobster` in [magpie](https://usemagpie.ai). About 30 models: DeepSeek, GLM,
|
|
6
|
+
Kimi, Qwen, MiniMax and Doubao.
|
|
7
|
+
|
|
8
|
+
**English** · [中文](README.zh-CN.md)
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
From npm:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
magpie plugin add opencode-lobster-auth
|
|
16
|
+
magpie plugin login lobster
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`magpie plugin update` picks up later versions.
|
|
20
|
+
|
|
21
|
+
To run a checkout instead of the published package, point at the folder:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
magpie plugin add /path/to/magpie-lobster
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Signing in
|
|
28
|
+
|
|
29
|
+
Two ways, either one is enough. Both name the account by the Youdao account
|
|
30
|
+
id (`yid`), so signing in the second way to an account already signed in the
|
|
31
|
+
first way replaces it in place.
|
|
32
|
+
|
|
33
|
+
- **Browser (a Youdao account).** The plugin serves a callback on
|
|
34
|
+
`127.0.0.1` and magpie opens Youdao's sign-in page. The browser comes back
|
|
35
|
+
with a code, which is traded for tokens at `/api/auth/exchange`. Kept as
|
|
36
|
+
an `oauth` sign-in holding the access and refresh tokens and their expiry,
|
|
37
|
+
so magpie renews it.
|
|
38
|
+
- **Remote browser.** For magpie on a server, in Docker, or anywhere the
|
|
39
|
+
browser is on another machine: the callback lands on the browser's own
|
|
40
|
+
`127.0.0.1` and never reaches magpie. The sign-in page's plain query names
|
|
41
|
+
the callback port, so magpie offers a **Callback URL** field: sign in, copy
|
|
42
|
+
the whole address the browser ends on, paste it there, and magpie sends it
|
|
43
|
+
to the port the plugin is listening on. The rest is the browser way.
|
|
44
|
+
- **The desktop's sign-in.** Uses the account LobsterAI's own desktop app is
|
|
45
|
+
signed in to. **No page is opened and nothing is typed.** The app's SQLite
|
|
46
|
+
database is read, never written:
|
|
47
|
+
- Windows: `%APPDATA%\LobsterAI\lobsterai.sqlite`
|
|
48
|
+
- macOS: `~/Library/Application Support/LobsterAI/lobsterai.sqlite`
|
|
49
|
+
- Linux: `~/.config/LobsterAI/lobsterai.sqlite`
|
|
50
|
+
|
|
51
|
+
A database that isn't there, or one the app keeps encrypted, fails this
|
|
52
|
+
way with a message saying so; use the browser way instead. The imported
|
|
53
|
+
identity is checked against the current token, and an incomplete or
|
|
54
|
+
conflicting identity fails closed rather than using another account's token.
|
|
55
|
+
|
|
56
|
+
Both are kept in magpie's `plugin-auth.json`.
|
|
57
|
+
|
|
58
|
+
### Why the import keeps no token
|
|
59
|
+
|
|
60
|
+
The desktop way stores **no credential at all** — `access` and `refresh` are
|
|
61
|
+
empty and `expires` is 0 — and reads the token from the app's database on
|
|
62
|
+
every request:
|
|
63
|
+
|
|
64
|
+
- A copied `refreshToken` would be shared with the app: whichever side
|
|
65
|
+
refreshes first signs the other out. Empty means neither disturbs the
|
|
66
|
+
other.
|
|
67
|
+
- `expires` of 0 means magpie never renews it — magpie renews only an
|
|
68
|
+
`oauth` sign-in whose expiry is set — so the app's session is never
|
|
69
|
+
touched.
|
|
70
|
+
- Reading it fresh means the plugin follows a token the app renewed, with no
|
|
71
|
+
need to sign in again.
|
|
72
|
+
|
|
73
|
+
The cost: if the app is signed out, uninstalled, or its database becomes
|
|
74
|
+
unreadable, the account stops working at once and magpie is told to ask for
|
|
75
|
+
a new sign-in. That is more honest than a token that quietly went stale.
|
|
76
|
+
|
|
77
|
+
### Renewal
|
|
78
|
+
|
|
79
|
+
A browser sign-in is renewed by magpie through the plugin's `auth.refresh`,
|
|
80
|
+
six hours before it ends. The desktop's sign-in is structurally never
|
|
81
|
+
renewed, as above.
|
|
82
|
+
|
|
83
|
+
The plugin never calls LobsterAI's own refresh endpoint to check or renew
|
|
84
|
+
anything: `/api/auth/refresh` may spend the account's real refresh token, so
|
|
85
|
+
it is deliberately left alone.
|
|
86
|
+
|
|
87
|
+
## Requests
|
|
88
|
+
|
|
89
|
+
Chats are chat completions at
|
|
90
|
+
`https://lobsterai-server.youdao.com/api/proxy/v1`. Three of LobsterAI's
|
|
91
|
+
habits are handled:
|
|
92
|
+
|
|
93
|
+
- **Every reply is SSE**, even one asked for with `stream: false`. A
|
|
94
|
+
non-streaming caller gets one `chat.completion` reassembled from it; a
|
|
95
|
+
streaming caller gets the events as they are.
|
|
96
|
+
- **Failures ride inside a 200.** LobsterAI puts errors in the stream
|
|
97
|
+
(`event: error`, or an error JSON record) and still answers 200, which
|
|
98
|
+
would leave a caller with nothing to fail over on. The plugin inspects the
|
|
99
|
+
early records, preserves plain-text error messages, and answers the status
|
|
100
|
+
it means: `40100` → 401, `40300` → 403, `42900` or rate-limit wording →
|
|
101
|
+
429, anything else → 400. Non-streaming requests also catch an error that
|
|
102
|
+
appears after an initial response chunk; streaming requests preserve such
|
|
103
|
+
an event after content has started. A sign-in LobsterAI turns down is a
|
|
104
|
+
genuine 401, passed through with magpie's lapse mark so the account is
|
|
105
|
+
flagged.
|
|
106
|
+
- **Requests carry the desktop client's headers**
|
|
107
|
+
(`X-LobsterAI-Client-Version`, `X-LobsterAI-Client-Capabilities`).
|
|
108
|
+
LobsterAI doesn't insist on them, but they name the feature set the plugin
|
|
109
|
+
speaks.
|
|
110
|
+
|
|
111
|
+
### Thinking levels
|
|
112
|
+
|
|
113
|
+
There are two kinds of model in LobsterAI's catalogue, and the plugin
|
|
114
|
+
speaks to each in its own way.
|
|
115
|
+
|
|
116
|
+
**8 models carry a `thinkingConfig`** (`deepseek-*`, `glm-*`), with the
|
|
117
|
+
levels `off`/`high`/`max`. Those go out in LobsterAI's own field:
|
|
118
|
+
`{"lobsterai_options":{"version":1,"thinking":{"level":"off"}}}`. Sending
|
|
119
|
+
that field to a model **without** a profile is a hard error (`code 4000`,
|
|
120
|
+
*model does not have a valid thinkingConfig*), so it is written only where
|
|
121
|
+
there is a profile to map it through. With a profile, only its declared
|
|
122
|
+
levels are offered; separately measured `reasoning_effort` levels apply only
|
|
123
|
+
while a model has no profile.
|
|
124
|
+
|
|
125
|
+
**10 models carry no profile** but still report `supportsThinking: true`.
|
|
126
|
+
For those the plugin declares levels and lets magpie's `reasoning_effort`
|
|
127
|
+
pass straight through, which LobsterAI honours.
|
|
128
|
+
|
|
129
|
+
Two things are worth knowing:
|
|
130
|
+
|
|
131
|
+
- **The lowest level is offered as `none`, not `off`.** magpie doesn't use
|
|
132
|
+
what a variant holds; it maps `reasoning_effort` onto a level name the
|
|
133
|
+
model declared, from a ladder of its own
|
|
134
|
+
(`none/minimal/low/medium/high/xhigh/max`). `off` is not on that ladder,
|
|
135
|
+
so a level declared as `off` can never be chosen. With a profile the
|
|
136
|
+
plugin declares `none` and turns it back into `off`; without one it sends
|
|
137
|
+
`none` as it is, since LobsterAI accepts it.
|
|
138
|
+
- **Levels differ per model and were measured, not assumed.** LobsterAI
|
|
139
|
+
refuses some of them outright: `MiniMax-M3.1-Flash` rejects `none`
|
|
140
|
+
(*requires adaptive thinking*), `qwen3.7-max` stops at `xhigh`, and
|
|
141
|
+
`qwen3.7-plus` answers `max` with an occasional 500. `MiniMax-M3` writes
|
|
142
|
+
its thinking into `<think>` tags inside the content and `reasoning_content`
|
|
143
|
+
stays empty; its effort isn't graded at all, so it offers only `none` and
|
|
144
|
+
`max`. A model whose levels were not measured declares none, and magpie
|
|
145
|
+
shows it as *thinks, no levels*.
|
|
146
|
+
|
|
147
|
+
Measured on `deepseek-flash`: `none` leaves `reasoning_content` at exactly
|
|
148
|
+
0 bytes, while `high`, `max` and no level at all don't — and on
|
|
149
|
+
`kimi-k3.1-pro`, `none` leaves it at 0 bytes while `xhigh` produces some.
|
|
150
|
+
|
|
151
|
+
### Kimi
|
|
152
|
+
|
|
153
|
+
Every Kimi model takes its sampling parameters from the server:
|
|
154
|
+
`temperature` must be 1, `top_p` 0.95 and the penalties 0, so the plugin
|
|
155
|
+
drops `temperature`, `top_p`, `n` and both penalties for the whole family —
|
|
156
|
+
it measured that for `kimi-k3`, `kimi-k3.1-pro` and `kimi-k2.8-preview`
|
|
157
|
+
alike. A client that always sends `temperature` (most agents do) would
|
|
158
|
+
otherwise be answered `50204 invalid temperature: only 1 is allowed`.
|
|
159
|
+
|
|
160
|
+
Only `kimi-k3` has `reasoning_effort` fixed to `max`. That forcing is
|
|
161
|
+
not extended to `kimi-k3.1-pro`, which honours the full ladder and would
|
|
162
|
+
otherwise have every level silently overridden. Separately, tool-call
|
|
163
|
+
history repair stays enabled for the `kimi-k3` family, including
|
|
164
|
+
`kimi-k3.1-pro`: an assistant tool call missing `reasoning_content` is given
|
|
165
|
+
an empty string without overwriting existing reasoning or the chosen effort.
|
|
166
|
+
|
|
167
|
+
`kimi-k3` is one of the models LobsterAI serves without listing it in every
|
|
168
|
+
catalogue revision. It is kept in the list this package declares for a
|
|
169
|
+
signed-out account, so `lobster/kimi-k3` stays offered.
|
|
170
|
+
|
|
171
|
+
## Models
|
|
172
|
+
|
|
173
|
+
Signed in, the list is the account's own (`/api/models/available`) — 18 or
|
|
174
|
+
19 usable models, each with its context window, whether it takes images,
|
|
175
|
+
and its thinking levels. Until then a static list of five is used, so the
|
|
176
|
+
provider is visible before signing in.
|
|
177
|
+
|
|
178
|
+
`costMultiplier` becomes magpie's `rate`. It is **time-of-day pricing**:
|
|
179
|
+
`deepseek-flash` is 0.05 in LobsterAI's off-peak hours and 0.1 in the peak
|
|
180
|
+
ones (09:00–12:00 and 14:00–18:00, Beijing time). The rate is read when the
|
|
181
|
+
list is, so it can be a while out of date.
|
|
182
|
+
|
|
183
|
+
The reply limit is 131,072. The catalogue's `maxTokens` field is always
|
|
184
|
+
null, so this was measured: all 18 models answered `max_tokens=131072`,
|
|
185
|
+
`glm-5.3` names `[1, 131072]` as its own range, and 524,288 is where
|
|
186
|
+
LobsterAI starts answering 500.
|
|
187
|
+
|
|
188
|
+
## Usage
|
|
189
|
+
|
|
190
|
+
`magpie quota` shows the credits left and when the soonest batch expires,
|
|
191
|
+
from `/api/user/quota` and `/api/user/profile-summary`. The credits are
|
|
192
|
+
reported as a **wallet window** (`余额·点数` with `runsOut`/`runsOutAt`)
|
|
193
|
+
rather than a plain string, so magpie can show the lapse and routing can
|
|
194
|
+
spend the account that runs out first. Every day's check-in adds 100 points
|
|
195
|
+
that lapse 30 days later, first in first out, so knowing the date matters.
|
|
196
|
+
The plugin reports `kept`: it renews nothing itself, so it must not claim a
|
|
197
|
+
renewal it didn't do.
|
|
198
|
+
|
|
199
|
+
Expired free allowances are not reported as a live window — the free period
|
|
200
|
+
ended in August 2026, and showing "6% used" looked like a usable allowance.
|
|
201
|
+
|
|
202
|
+
## Daily check-in
|
|
203
|
+
|
|
204
|
+
The plugin implements `auth.checkin`, so magpie can claim LobsterAI's daily
|
|
205
|
+
gift (100 points) for you. **It is off by default**: turn it on under
|
|
206
|
+
Settings → Usage → Plugins. Once on, every signed-in LobsterAI account is
|
|
207
|
+
checked in once a day (Beijing time).
|
|
208
|
+
|
|
209
|
+
This is the only way to collect those points without opening LobsterAI's own
|
|
210
|
+
desktop app, whose sidebar holds the check-in. The claim is idempotent — a
|
|
211
|
+
day already claimed answers `51104`, and the plugin reports `done` rather
|
|
212
|
+
than claiming again — and every claim is confirmed by re-reading the state
|
|
213
|
+
before it is reported as `claimed`. HTTP failures, malformed business
|
|
214
|
+
responses and invalid state are reported as `failed`, not as an inactive
|
|
215
|
+
activity or an ineligible account, so magpie can retry later that day.
|
|
216
|
+
|
|
217
|
+
## Not here
|
|
218
|
+
|
|
219
|
+
- **Company accounts.** LobsterAI sends them through a different page
|
|
220
|
+
(`EnterpriseIdentitySelect`). Only personal accounts are handled.
|
|
221
|
+
- **A verified `/api/auth/refresh`.** Calling it may spend the account's
|
|
222
|
+
real refresh token, so it is not exercised. The renewal path is written
|
|
223
|
+
from the app's own code and has not been confirmed against the service.
|
|
224
|
+
- **OpenCode.** The plugin is written in OpenCode's provider-plugin format —
|
|
225
|
+
that is the format magpie loads — but it has only been exercised in magpie.
|
|
226
|
+
`auth.refresh`, `auth.refreshLead`, `auth.checkin` and the `magpie` field
|
|
227
|
+
in `package.json` are magpie's own, which OpenCode ignores.
|
|
228
|
+
|
|
229
|
+
## Several accounts
|
|
230
|
+
|
|
231
|
+
magpie can hold more than one sign-in per provider, and this plugin supports
|
|
232
|
+
that: sign in to a second Youdao account through the browser and both are
|
|
233
|
+
listed.
|
|
234
|
+
|
|
235
|
+
The desktop import is the exception to watch. LobsterAI's desktop app has one
|
|
236
|
+
signed-in account at a time, and the plugin reads that app's token on every
|
|
237
|
+
request. So an account imported from the desktop is tied to the account that
|
|
238
|
+
was signed in when it was imported: switch the desktop to another account,
|
|
239
|
+
and the imported one is marked *signed out · sign in again* rather than
|
|
240
|
+
quietly spending the other account's credits. Sign it in again, or use the
|
|
241
|
+
browser sign-in for accounts you want to keep independent.
|
|
242
|
+
|
|
243
|
+
## Development
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
bun test
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The entry point only assembles the hooks. The logic lives in `lib/` as plain
|
|
250
|
+
functions and is reached through `_internal`, because magpie and OpenCode
|
|
251
|
+
both treat every exported function as a plugin of its own.
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
MIT
|