dsh-plugin-subscription-oauth 0.8.17 → 0.9.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/lib/auth/mimo-flow.d.ts +117 -0
- package/lib/auth/mimo-flow.js +355 -0
- package/lib/client/FastModeButton.js +6 -7
- package/lib/client.js +3 -6
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +14 -2
- package/lib/protocol.d.ts +8 -0
- package/lib/providers/grok.js +10 -7
- package/lib/providers/kimi-provider.js +1 -1
- package/lib/providers/kimi.js +7 -4
- package/lib/providers/mimo-provider.d.ts +35 -0
- package/lib/providers/mimo-provider.js +170 -0
- package/lib/providers/mimo.d.ts +96 -0
- package/lib/providers/mimo.js +260 -0
- package/package.json +1 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Xiaomi MiMo wire adapter serving the `mimo` route.
|
|
3
|
+
*
|
|
4
|
+
* Model requests are chat completions at the MiMo server's `/route`, carrying the
|
|
5
|
+
* session cookies the sign-on set — not a bearer token. Two models ship: MiMo Pro
|
|
6
|
+
* and MiMo Flash. The open platform's Token Plan spends its own `tp-` key at a
|
|
7
|
+
* separate endpoint; that fallback lives beside the account-level stream here.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-plugin-subscription-oauth/providers/mimo
|
|
10
|
+
*/
|
|
11
|
+
import { LlmAdapter, errorChain, } from '@deepseek-ai/dsh-llm';
|
|
12
|
+
import { httpLlmError, isMissingOrInvalidCredential, mapFetchFailure, idleWatchdog } from './common.js';
|
|
13
|
+
import { proxiedFetch } from '../http.js';
|
|
14
|
+
import { refreshMimoSession } from '../auth/mimo-flow.js';
|
|
15
|
+
/** The `mimo` provider route key. */
|
|
16
|
+
export const MIMO_PROVIDER_ID = 'mimo';
|
|
17
|
+
/** Human name for the route and its cards. */
|
|
18
|
+
export const MIMO_PROVIDER_NAME = 'Xiaomi MiMo';
|
|
19
|
+
/** Where the Settings page sends someone to get an account. */
|
|
20
|
+
export const MIMO_CODE_URL = 'https://platform.xiaomimimo.com';
|
|
21
|
+
/** MiMo context window shared by Pro and Flash. */
|
|
22
|
+
export const MIMO_CONTEXT_WINDOW = 1_000_000;
|
|
23
|
+
/** The output ceiling MiMo advertises. */
|
|
24
|
+
export const MIMO_DEFAULT_MAX_TOKENS = 128_000;
|
|
25
|
+
/** The model the picker preselects. */
|
|
26
|
+
export const MIMO_DEFAULT_MODEL = 'mimo-pro';
|
|
27
|
+
/** Validate one stored value as a MiMo session, or undefined when it is not one. */
|
|
28
|
+
export function parseMimoSession(value) {
|
|
29
|
+
if (typeof value !== 'object' || value === null)
|
|
30
|
+
return undefined;
|
|
31
|
+
const record = value;
|
|
32
|
+
if (typeof record.accessToken !== 'string' || record.accessToken.length === 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (typeof record.expiresAt !== 'number')
|
|
35
|
+
return undefined;
|
|
36
|
+
if (typeof record.apiBase !== 'string' || record.apiBase.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (typeof record.mimoCreds !== 'string')
|
|
39
|
+
return undefined;
|
|
40
|
+
if (typeof record.mimoCookies !== 'string')
|
|
41
|
+
return undefined;
|
|
42
|
+
return {
|
|
43
|
+
accessToken: record.accessToken,
|
|
44
|
+
expiresAt: record.expiresAt,
|
|
45
|
+
apiBase: record.apiBase,
|
|
46
|
+
mimoCreds: record.mimoCreds,
|
|
47
|
+
mimoCookies: record.mimoCookies,
|
|
48
|
+
...typeof record.refreshToken === 'string' ? { refreshToken: record.refreshToken } : {},
|
|
49
|
+
...typeof record.accountId === 'string' ? { accountId: record.accountId } : {},
|
|
50
|
+
...typeof record.email === 'string' ? { email: record.email } : {},
|
|
51
|
+
...typeof record.region === 'string' ? { region: record.region } : {},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** The two models the MiMo app serves. */
|
|
55
|
+
export const MIMO_MODELS = [
|
|
56
|
+
{ id: 'mimo-pro', name: 'MiMo Pro', description: 'Flagship MiMo model', contextWindow: MIMO_CONTEXT_WINDOW },
|
|
57
|
+
{ id: 'mimo-flash', name: 'MiMo Flash', description: 'Fast MiMo model', contextWindow: MIMO_CONTEXT_WINDOW },
|
|
58
|
+
];
|
|
59
|
+
/** The MiMo wire adapter serving the `mimo` route. */
|
|
60
|
+
export class MimoAdapter extends LlmAdapter {
|
|
61
|
+
options;
|
|
62
|
+
fetchFn;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
super();
|
|
65
|
+
this.options = options;
|
|
66
|
+
this.fetchFn = options.fetchFn ?? proxiedFetch;
|
|
67
|
+
}
|
|
68
|
+
providerInfo(provider) {
|
|
69
|
+
return { id: provider, name: MIMO_PROVIDER_NAME };
|
|
70
|
+
}
|
|
71
|
+
async listModels(_provider) {
|
|
72
|
+
if (await this.options.tokens.session() === undefined)
|
|
73
|
+
return [];
|
|
74
|
+
const hidden = await this.hidden();
|
|
75
|
+
return MIMO_MODELS.filter((entry) => !hidden.has(entry.id)).map((entry) => ({
|
|
76
|
+
provider: MIMO_PROVIDER_ID,
|
|
77
|
+
id: entry.id,
|
|
78
|
+
name: entry.name,
|
|
79
|
+
description: entry.description,
|
|
80
|
+
inputModalities: ['text', 'image'],
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
async hidden() {
|
|
84
|
+
if (this.options.hiddenModels === undefined)
|
|
85
|
+
return new Set();
|
|
86
|
+
try {
|
|
87
|
+
return await this.options.hiddenModels();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return new Set();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async resolveModel(_provider, model) {
|
|
94
|
+
const entry = MIMO_MODELS.find((candidate) => candidate.id === model);
|
|
95
|
+
if (entry === undefined) {
|
|
96
|
+
return {
|
|
97
|
+
provider: MIMO_PROVIDER_ID,
|
|
98
|
+
id: model,
|
|
99
|
+
name: model,
|
|
100
|
+
context: { contextWindow: MIMO_CONTEXT_WINDOW },
|
|
101
|
+
defaultMaxTokens: MIMO_DEFAULT_MAX_TOKENS,
|
|
102
|
+
inputModalities: ['text', 'image'],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
provider: MIMO_PROVIDER_ID,
|
|
107
|
+
id: entry.id,
|
|
108
|
+
name: entry.name,
|
|
109
|
+
description: entry.description,
|
|
110
|
+
context: { contextWindow: entry.contextWindow },
|
|
111
|
+
defaultMaxTokens: MIMO_DEFAULT_MAX_TOKENS,
|
|
112
|
+
inputModalities: ['text', 'image'],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
providerRetryPolicy() {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
/** The catalog the Settings list reads: every shipped model. */
|
|
119
|
+
catalogEntries() {
|
|
120
|
+
return MIMO_MODELS;
|
|
121
|
+
}
|
|
122
|
+
/** The ids the route itself serves, without hand-added extras. */
|
|
123
|
+
ownCatalogEntries() {
|
|
124
|
+
return MIMO_MODELS;
|
|
125
|
+
}
|
|
126
|
+
async *stream(options) {
|
|
127
|
+
const held = await this.options.tokens.session();
|
|
128
|
+
if (held === undefined)
|
|
129
|
+
throw await httpLlmError(new Response(null, { status: 401 }), 'Xiaomi MiMo');
|
|
130
|
+
const session = held.session;
|
|
131
|
+
const watchdog = idleWatchdog(options.signal, this.options.streamIdleTimeoutMs);
|
|
132
|
+
try {
|
|
133
|
+
const cookies = this.cookieHeader(session);
|
|
134
|
+
const body = this.requestBody(options);
|
|
135
|
+
const response = await this.fetchFn(`${session.apiBase.replace(/\/+$/, '')}/route/chat/completions`, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: {
|
|
138
|
+
'Content-Type': 'application/json',
|
|
139
|
+
'Accept': 'text/event-stream',
|
|
140
|
+
'Cookie': cookies,
|
|
141
|
+
'X-Mimo-Source': 'mimocode-cli-free',
|
|
142
|
+
'User-Agent': 'miNative PC/Normal Windows_NT/10.0.26100 SDKV/1.0.0 DEVT/PC DEVS/Windows APP/miaccount_desktop APPV/0.1.0',
|
|
143
|
+
'X-Client-Version': '26.929.292248',
|
|
144
|
+
},
|
|
145
|
+
body: JSON.stringify(body),
|
|
146
|
+
signal: watchdog.signal,
|
|
147
|
+
});
|
|
148
|
+
if (!response.ok || response.body === null) {
|
|
149
|
+
throw await httpLlmError(response, 'Xiaomi MiMo');
|
|
150
|
+
}
|
|
151
|
+
yield* this.readStream(response, watchdog);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
throw mapFetchFailure('Xiaomi MiMo', error, watchdog, options.signal);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
watchdog.stop();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The session cookies as one Cookie header, in a steady order.
|
|
162
|
+
*
|
|
163
|
+
* MiMo is a cookie session, not a bearer: the wire credential is the cookie
|
|
164
|
+
* header built from the stored session's cookie record.
|
|
165
|
+
*/
|
|
166
|
+
cookieHeader(session) {
|
|
167
|
+
const first = ['serviceToken', 'userId', 'cUserId'];
|
|
168
|
+
try {
|
|
169
|
+
const cookies = JSON.parse(session.mimoCookies);
|
|
170
|
+
const parts = first.filter((key) => key in cookies).map((key) => `${key}=${cookies[key]}`);
|
|
171
|
+
for (const [key, value] of Object.entries(cookies)) {
|
|
172
|
+
if (!first.includes(key))
|
|
173
|
+
parts.push(`${key}=${value}`);
|
|
174
|
+
}
|
|
175
|
+
return parts.join('; ');
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return '';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
requestBody(options) {
|
|
182
|
+
const messages = [];
|
|
183
|
+
if (options.system !== undefined && options.system.trim() !== '') {
|
|
184
|
+
messages.push({ role: 'system', content: options.system });
|
|
185
|
+
}
|
|
186
|
+
for (const message of options.messages) {
|
|
187
|
+
messages.push(this.toChatMessage(message));
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
model: options.model,
|
|
191
|
+
messages,
|
|
192
|
+
...options.maxTokens === undefined ? {} : { max_tokens: options.maxTokens },
|
|
193
|
+
stream: true,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
toChatMessage(message) {
|
|
197
|
+
const role = message.role === 'assistant' ? 'assistant' : 'user';
|
|
198
|
+
const textParts = [];
|
|
199
|
+
for (const block of message.content) {
|
|
200
|
+
if (block.type === 'text')
|
|
201
|
+
textParts.push(block.text);
|
|
202
|
+
}
|
|
203
|
+
return { role, content: textParts.join('\n') };
|
|
204
|
+
}
|
|
205
|
+
async *readStream(response, watchdog) {
|
|
206
|
+
const reader = response.body.getReader();
|
|
207
|
+
const decoder = new TextDecoder();
|
|
208
|
+
let buffer = '';
|
|
209
|
+
let started = false;
|
|
210
|
+
try {
|
|
211
|
+
while (true) {
|
|
212
|
+
const { done, value } = await reader.read();
|
|
213
|
+
if (done)
|
|
214
|
+
break;
|
|
215
|
+
watchdog.pulse();
|
|
216
|
+
buffer += decoder.decode(value, { stream: true });
|
|
217
|
+
const frames = buffer.split('\n\n');
|
|
218
|
+
buffer = frames.pop() ?? '';
|
|
219
|
+
for (const frame of frames) {
|
|
220
|
+
const data = frame
|
|
221
|
+
.split('\n')
|
|
222
|
+
.filter((line) => line.startsWith('data:'))
|
|
223
|
+
.map((line) => line.slice(5).trim())
|
|
224
|
+
.join('\n');
|
|
225
|
+
if (data === '' || data === '[DONE]')
|
|
226
|
+
continue;
|
|
227
|
+
let chunk;
|
|
228
|
+
try {
|
|
229
|
+
chunk = JSON.parse(data);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const delta = chunk.choices?.[0]?.delta?.content;
|
|
235
|
+
if (typeof delta === 'string' && delta !== '') {
|
|
236
|
+
if (!started) {
|
|
237
|
+
yield { type: 'block-start', index: 0, blockType: 'text' };
|
|
238
|
+
started = true;
|
|
239
|
+
}
|
|
240
|
+
yield { type: 'text-delta', index: 0, text: delta };
|
|
241
|
+
}
|
|
242
|
+
if (chunk.usage !== undefined) {
|
|
243
|
+
const input = chunk.usage.prompt_tokens ?? 0;
|
|
244
|
+
const output = chunk.usage.completion_tokens ?? 0;
|
|
245
|
+
yield {
|
|
246
|
+
type: 'usage',
|
|
247
|
+
usage: { inputTokens: input, outputTokens: output, totalTokens: chunk.usage.total_tokens ?? input + output },
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (started)
|
|
253
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: '' } };
|
|
254
|
+
yield { type: 'finish', reason: { kind: 'stop' } };
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
reader.cancel().catch(() => { });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subscription-oauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Use subscription accounts as DeepSeek Harness LLM providers — Kimi Code (OAuth device flow), Grok / X Premium (OIDC + PKCE), Google Antigravity (Google OAuth + Cloud Code Assist) and GLM / BigModel (ZCode's browser sign-in) — with per-model visibility, drag-to-reorder Auto fallback and plan quotas from the web Settings page",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "EricFL1998",
|