opencode-lobster-auth 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/README.md +181 -170
- package/README.zh-CN.md +156 -147
- package/index.mjs +210 -191
- package/lib/auth-login.mjs +154 -131
- package/lib/constants.mjs +41 -41
- package/lib/jwt.mjs +34 -34
- package/lib/sse.mjs +67 -67
- package/lib/usage.mjs +41 -41
- package/package.json +22 -9
package/README.md
CHANGED
|
@@ -1,170 +1,181 @@
|
|
|
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
|
-
A folder plugin — it is not published to npm:
|
|
13
|
-
|
|
14
|
-
```sh
|
|
15
|
-
magpie plugin add /path/to/magpie-lobster
|
|
16
|
-
magpie plugin login lobster
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Signing in
|
|
20
|
-
|
|
21
|
-
Two ways, either one is enough. Both name the account by the Youdao account
|
|
22
|
-
id (`yid`), so signing in the second way to an account already signed in the
|
|
23
|
-
first way replaces it in place.
|
|
24
|
-
|
|
25
|
-
- **Browser (a Youdao account).** The plugin serves a callback on
|
|
26
|
-
`127.0.0.1` and magpie opens Youdao's sign-in page. The browser comes back
|
|
27
|
-
with a code, which is traded for tokens at `/api/auth/exchange`. Kept as
|
|
28
|
-
an `oauth` sign-in holding the access and refresh tokens and their expiry,
|
|
29
|
-
so magpie renews it.
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
is
|
|
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
|
-
|
|
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
|
+
A folder plugin — it is not published to npm:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
magpie plugin add /path/to/magpie-lobster
|
|
16
|
+
magpie plugin login lobster
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Signing in
|
|
20
|
+
|
|
21
|
+
Two ways, either one is enough. Both name the account by the Youdao account
|
|
22
|
+
id (`yid`), so signing in the second way to an account already signed in the
|
|
23
|
+
first way replaces it in place.
|
|
24
|
+
|
|
25
|
+
- **Browser (a Youdao account).** The plugin serves a callback on
|
|
26
|
+
`127.0.0.1` and magpie opens Youdao's sign-in page. The browser comes back
|
|
27
|
+
with a code, which is traded for tokens at `/api/auth/exchange`. Kept as
|
|
28
|
+
an `oauth` sign-in holding the access and refresh tokens and their expiry,
|
|
29
|
+
so magpie renews it.
|
|
30
|
+
- **Remote browser.** For magpie on a server, in Docker, or anywhere the
|
|
31
|
+
browser is on another machine: the callback lands on the browser's own
|
|
32
|
+
`127.0.0.1` and never reaches magpie. The sign-in page's plain query names
|
|
33
|
+
the callback port, so magpie offers a **Callback URL** field: sign in, copy
|
|
34
|
+
the whole address the browser ends on, paste it there, and magpie sends it
|
|
35
|
+
to the port the plugin is listening on. The rest is the browser way.
|
|
36
|
+
- **The desktop's sign-in.** Uses the account LobsterAI's own desktop app is
|
|
37
|
+
signed in to. **No page is opened and nothing is typed.** The app's SQLite
|
|
38
|
+
database is read, never written:
|
|
39
|
+
- Windows: `%APPDATA%\LobsterAI\lobsterai.sqlite`
|
|
40
|
+
- macOS: `~/Library/Application Support/LobsterAI/lobsterai.sqlite`
|
|
41
|
+
- Linux: `~/.config/LobsterAI/lobsterai.sqlite`
|
|
42
|
+
|
|
43
|
+
A database that isn't there, or one the app keeps encrypted, fails this
|
|
44
|
+
way with a message saying so; use the browser way instead.
|
|
45
|
+
|
|
46
|
+
Both are kept in magpie's `plugin-auth.json`.
|
|
47
|
+
|
|
48
|
+
### Why the import keeps no token
|
|
49
|
+
|
|
50
|
+
The desktop way stores **no credential at all** — `access` and `refresh` are
|
|
51
|
+
empty and `expires` is 0 — and reads the token from the app's database on
|
|
52
|
+
every request:
|
|
53
|
+
|
|
54
|
+
- A copied `refreshToken` would be shared with the app: whichever side
|
|
55
|
+
refreshes first signs the other out. Empty means neither disturbs the
|
|
56
|
+
other.
|
|
57
|
+
- `expires` of 0 means magpie never renews it — magpie renews only an
|
|
58
|
+
`oauth` sign-in whose expiry is set — so the app's session is never
|
|
59
|
+
touched.
|
|
60
|
+
- Reading it fresh means the plugin follows a token the app renewed, with no
|
|
61
|
+
need to sign in again.
|
|
62
|
+
|
|
63
|
+
The cost: if the app is signed out, uninstalled, or its database becomes
|
|
64
|
+
unreadable, the account stops working at once and magpie is told to ask for
|
|
65
|
+
a new sign-in. That is more honest than a token that quietly went stale.
|
|
66
|
+
|
|
67
|
+
### Renewal
|
|
68
|
+
|
|
69
|
+
A browser sign-in is renewed by magpie through the plugin's `auth.refresh`,
|
|
70
|
+
six hours before it ends. The desktop's sign-in is structurally never
|
|
71
|
+
renewed, as above.
|
|
72
|
+
|
|
73
|
+
The plugin never calls LobsterAI's own refresh endpoint to check or renew
|
|
74
|
+
anything: `/api/auth/refresh` may spend the account's real refresh token, so
|
|
75
|
+
it is deliberately left alone.
|
|
76
|
+
|
|
77
|
+
## Requests
|
|
78
|
+
|
|
79
|
+
Chats are chat completions at
|
|
80
|
+
`https://lobsterai-server.youdao.com/api/proxy/v1`. Three of LobsterAI's
|
|
81
|
+
habits are handled:
|
|
82
|
+
|
|
83
|
+
- **Every reply is SSE**, even one asked for with `stream: false`. A
|
|
84
|
+
non-streaming caller gets one `chat.completion` reassembled from it; a
|
|
85
|
+
streaming caller gets the events as they are.
|
|
86
|
+
- **Failures ride inside a 200.** LobsterAI puts an error in the stream
|
|
87
|
+
(`event: error`) and still answers 200, which would leave a caller with
|
|
88
|
+
nothing to fail over on. The plugin reads the first record and answers the
|
|
89
|
+
status it means: `40100` → 401, `40300` → 403, `42900` or rate-limit
|
|
90
|
+
wording → 429, anything else → 400. A sign-in LobsterAI turns down is a
|
|
91
|
+
genuine 401, passed through with magpie's lapse mark so the account is
|
|
92
|
+
flagged.
|
|
93
|
+
- **Requests carry the desktop client's headers**
|
|
94
|
+
(`X-LobsterAI-Client-Version`, `X-LobsterAI-Client-Capabilities`).
|
|
95
|
+
LobsterAI doesn't insist on them, but they name the feature set the plugin
|
|
96
|
+
speaks.
|
|
97
|
+
|
|
98
|
+
### Thinking levels
|
|
99
|
+
|
|
100
|
+
A model's `thinkingConfig` gives it levels — `off`, `high`, `max` in the
|
|
101
|
+
current catalogue — and they reach the wire in LobsterAI's own field:
|
|
102
|
+
`{"lobsterai_options":{"version":1,"thinking":{"level":"off"}}}`.
|
|
103
|
+
|
|
104
|
+
Two things are worth knowing:
|
|
105
|
+
|
|
106
|
+
- **`lobsterai_options` is only for models that have a thinking profile.**
|
|
107
|
+
21 of the 29 models have none, and sending the field to one of those is a
|
|
108
|
+
hard error (`code 4000`, *model does not have a valid thinkingConfig*), so
|
|
109
|
+
the whole chat fails. A level is passed on only where there is a profile
|
|
110
|
+
to map it through.
|
|
111
|
+
- **The lowest level is offered as `none`, not `off`.** magpie doesn't use
|
|
112
|
+
what a variant holds; it maps `reasoning_effort` onto a level name the
|
|
113
|
+
model declared, from a ladder of its own
|
|
114
|
+
(`none/minimal/low/medium/high/xhigh/max`). `off` is not on that ladder,
|
|
115
|
+
so a level declared as `off` can never be chosen. The plugin declares
|
|
116
|
+
`none` and turns it back into `off` for LobsterAI.
|
|
117
|
+
|
|
118
|
+
Measured on `deepseek-flash`: `none` leaves `reasoning_content` at exactly
|
|
119
|
+
0 bytes, while `high`, `max` and no level at all don't.
|
|
120
|
+
|
|
121
|
+
### Kimi K3
|
|
122
|
+
|
|
123
|
+
LobsterAI's `kimi-k3` takes its sampling parameters from the server, so
|
|
124
|
+
`temperature`, `top_p`, `n` and the penalties are dropped, `reasoning_effort`
|
|
125
|
+
is fixed to `max`, and an assistant tool call missing `reasoning_content` is
|
|
126
|
+
given an empty one.
|
|
127
|
+
|
|
128
|
+
`kimi-k3` is one of the models LobsterAI serves but does not list:
|
|
129
|
+
`/api/models/available` returns 29 models and none of them is K3, yet a chat
|
|
130
|
+
against `kimi-k3` answers normally. It is kept in the list this package
|
|
131
|
+
declares for a signed-out account, so `lobster/kimi-k3` is offered — and the
|
|
132
|
+
handling above is live, not a precaution.
|
|
133
|
+
|
|
134
|
+
## Models
|
|
135
|
+
|
|
136
|
+
Signed in, the list is the account's own (`/api/models/available`): 29
|
|
137
|
+
models, each with its context window, whether it takes images, and its
|
|
138
|
+
thinking levels. Until then a static list of five is used, so the provider
|
|
139
|
+
is visible before signing in.
|
|
140
|
+
|
|
141
|
+
`costMultiplier` becomes magpie's `rate`. It is **time-of-day pricing**:
|
|
142
|
+
`deepseek-flash` is 0.05 in LobsterAI's off-peak hours and 0.1 in the peak
|
|
143
|
+
ones (09:00–12:00 and 14:00–18:00, Beijing time). The rate is read when the
|
|
144
|
+
list is, so it can be a while out of date.
|
|
145
|
+
|
|
146
|
+
## Usage
|
|
147
|
+
|
|
148
|
+
`magpie quota` shows the free allowance, when it ends and the credits left,
|
|
149
|
+
from `/api/user/quota` and `/api/user/profile-summary`. The plugin reports
|
|
150
|
+
`kept`: it renews nothing itself, so it must not claim a renewal it didn't
|
|
151
|
+
do.
|
|
152
|
+
|
|
153
|
+
## Not here
|
|
154
|
+
|
|
155
|
+
- **Company accounts.** LobsterAI sends them through a different page
|
|
156
|
+
(`EnterpriseIdentitySelect`). Only personal accounts are handled.
|
|
157
|
+
- **A verified `/api/auth/refresh`.** Calling it may spend the account's
|
|
158
|
+
real refresh token, so it is not exercised. The renewal path is written
|
|
159
|
+
from the app's own code and has not been confirmed against the service.
|
|
160
|
+
- **An exact output limit.** The catalogue has no such field. The plugin
|
|
161
|
+
declares a conservative 32,000; measured, LobsterAI takes 262,144 and
|
|
162
|
+
answers 500 at 524,288, so the real ceiling is somewhere between.
|
|
163
|
+
- **OpenCode.** The plugin is written in OpenCode's provider-plugin format —
|
|
164
|
+
that is the format magpie loads — but it has only been exercised in magpie.
|
|
165
|
+
`auth.refresh`, `auth.refreshLead` and the `magpie` field in `package.json`
|
|
166
|
+
are magpie's own, which OpenCode ignores.
|
|
167
|
+
- **Several accounts at once.** magpie keeps one sign-in per provider.
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
bun test
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The entry point only assembles the hooks. The logic lives in `lib/` as plain
|
|
176
|
+
functions and is reached through `_internal`, because magpie and OpenCode
|
|
177
|
+
both treat every exported function as a plugin of its own.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT
|