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