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
package/lib/index.d.ts
CHANGED
|
@@ -16,9 +16,9 @@ export declare const name = "dsh-plugin-subscription-oauth";
|
|
|
16
16
|
/** The provider routes need the LLM runtime; everything else is optional. */
|
|
17
17
|
export declare const inject: string[];
|
|
18
18
|
/** Every route key this plugin accepts in configuration. */
|
|
19
|
-
export declare const PROVIDER_IDS: readonly ["kimi", "grok", "antigravity", "glm", "ark"];
|
|
19
|
+
export declare const PROVIDER_IDS: readonly ["kimi", "grok", "antigravity", "glm", "ark", "mimo"];
|
|
20
20
|
/** The routes this build can actually register. */
|
|
21
|
-
export declare const IMPLEMENTED_PROVIDER_IDS: readonly ["kimi", "grok", "antigravity", "glm", "ark"];
|
|
21
|
+
export declare const IMPLEMENTED_PROVIDER_IDS: readonly ["kimi", "grok", "antigravity", "glm", "ark", "mimo"];
|
|
22
22
|
/** One route key. */
|
|
23
23
|
export type ProviderId = typeof PROVIDER_IDS[number];
|
|
24
24
|
/** Plugin configuration, as accepted from `cordis.patch.yml`. */
|
package/lib/index.js
CHANGED
|
@@ -22,15 +22,16 @@ import { createGrokProvider } from './providers/grok.js';
|
|
|
22
22
|
import { createKimiProvider } from './providers/kimi-provider.js';
|
|
23
23
|
import { createGlmProvider } from './providers/glm-provider.js';
|
|
24
24
|
import { createArkProvider } from './providers/ark-provider.js';
|
|
25
|
+
import { createMimoProvider } from './providers/mimo-provider.js';
|
|
25
26
|
import { ModelPreferences } from './settings.js';
|
|
26
27
|
/** Plugin id; also the name every effect label and log line is prefixed with. */
|
|
27
28
|
export const name = 'dsh-plugin-subscription-oauth';
|
|
28
29
|
/** The provider routes need the LLM runtime; everything else is optional. */
|
|
29
30
|
export const inject = ['llm'];
|
|
30
31
|
/** Every route key this plugin accepts in configuration. */
|
|
31
|
-
export const PROVIDER_IDS = ['kimi', 'grok', 'antigravity', 'glm', 'ark'];
|
|
32
|
+
export const PROVIDER_IDS = ['kimi', 'grok', 'antigravity', 'glm', 'ark', 'mimo'];
|
|
32
33
|
/** The routes this build can actually register. */
|
|
33
|
-
export const IMPLEMENTED_PROVIDER_IDS = ['kimi', 'grok', 'antigravity', 'glm', 'ark'];
|
|
34
|
+
export const IMPLEMENTED_PROVIDER_IDS = ['kimi', 'grok', 'antigravity', 'glm', 'ark', 'mimo'];
|
|
34
35
|
/** Configuration schema, with the config file's own defaults applied. */
|
|
35
36
|
export const Config = z.object({
|
|
36
37
|
providers: z.array(z.union([...PROVIDER_IDS])).default([...IMPLEMENTED_PROVIDER_IDS]),
|
|
@@ -138,6 +139,17 @@ export function apply(ctx, config = {}) {
|
|
|
138
139
|
fetchFn: proxiedFetch,
|
|
139
140
|
}));
|
|
140
141
|
break;
|
|
142
|
+
case 'mimo':
|
|
143
|
+
routes.push(createMimoProvider({
|
|
144
|
+
credentialFile,
|
|
145
|
+
streamIdleTimeoutMs,
|
|
146
|
+
preferences,
|
|
147
|
+
resolveAttachments: () => ctx.get('attachments'),
|
|
148
|
+
onCatalogChanged: () => { reannounce('mimo'); },
|
|
149
|
+
onWarn,
|
|
150
|
+
fetchFn: proxiedFetch,
|
|
151
|
+
}));
|
|
152
|
+
break;
|
|
141
153
|
default:
|
|
142
154
|
// A configured route with no module yet must not silently disappear.
|
|
143
155
|
onWarn(`provider ${id} is configured but not implemented in this build`);
|
package/lib/protocol.d.ts
CHANGED
|
@@ -364,6 +364,14 @@ export interface ModelVariants {
|
|
|
364
364
|
* already the fast variant, which is not itself a listed row.
|
|
365
365
|
*/
|
|
366
366
|
fastBase?: string;
|
|
367
|
+
/**
|
|
368
|
+
* The reasoning rung that selects the fast variant.
|
|
369
|
+
*
|
|
370
|
+
* Each provider names its own variant (xAI `lightspeed`, Kimi `highspeed`), so the
|
|
371
|
+
* rung is the provider's word — the picker reads it, and the stream maps it back to
|
|
372
|
+
* the variant's wire id. Defaults to `fast`.
|
|
373
|
+
*/
|
|
374
|
+
fastRung?: string;
|
|
367
375
|
/** Image-generation model wire id, when the provider reported one. */
|
|
368
376
|
image?: string;
|
|
369
377
|
}
|
package/lib/providers/grok.js
CHANGED
|
@@ -51,16 +51,19 @@ export const GROK_DEFAULT_MAX_TOKENS = 32_000;
|
|
|
51
51
|
export const GROK_PREEMPT_MS = 2 * 60 * 1000;
|
|
52
52
|
/** Hosts an authorization server endpoint may live on. */
|
|
53
53
|
const TRUSTED_AUTH_HOSTS = new Set(['auth.x.ai', 'accounts.x.ai']);
|
|
54
|
+
/** The rung id the composer's quick-mode button selects. */
|
|
55
|
+
const FAST_RUNG = 'lightspeed';
|
|
54
56
|
/** Display metadata for the reasoning ladder xAI documents. */
|
|
55
57
|
const EFFORT_LABELS = {
|
|
56
58
|
none: { name: 'Off', description: 'Answer without spending thinking tokens' },
|
|
57
59
|
low: { name: 'Low', description: 'Short chain of thought' },
|
|
58
60
|
medium: { name: 'Medium', description: 'Balanced thinking budget' },
|
|
59
61
|
high: { name: 'High', description: 'Deep chain of thought' },
|
|
60
|
-
// Not a thinking budget: this rung is the model's fast variant, which the
|
|
61
|
-
// quick-mode button selects. It rides the effort channel so the row keeps
|
|
62
|
-
// a swapped model id would leave the picker with no way back
|
|
63
|
-
|
|
62
|
+
// Not a thinking budget: this rung is the model's fast/lightspeed variant, which the
|
|
63
|
+
// composer's quick-mode button selects. It rides the effort channel so the row keeps
|
|
64
|
+
// its own id — a swapped model id would leave the picker with no way back. xAI's own
|
|
65
|
+
// word for it is "lightspeed", which is what the rung is named here.
|
|
66
|
+
[FAST_RUNG]: { name: 'Lightspeed', description: 'xAI lightspeed variant of this model' },
|
|
64
67
|
};
|
|
65
68
|
/**
|
|
66
69
|
* Curated catalog entries.
|
|
@@ -135,7 +138,7 @@ function effortInfo(effort) {
|
|
|
135
138
|
*/
|
|
136
139
|
function reasoningInfo(entry, fast = false) {
|
|
137
140
|
const efforts = entry.reasoningEfforts;
|
|
138
|
-
const ids = fast ? [...efforts ?? [],
|
|
141
|
+
const ids = fast ? [...efforts ?? [], FAST_RUNG] : efforts;
|
|
139
142
|
if (ids === undefined || ids.length === 0)
|
|
140
143
|
return undefined;
|
|
141
144
|
return {
|
|
@@ -520,7 +523,7 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
520
523
|
// Fast mode rides the reasoning channel: `fast` is a rung of the row, not a model id
|
|
521
524
|
// of its own, so the row the picker selected stays dispatchable and the user can
|
|
522
525
|
// always select the standard model again. Only the wire id changes.
|
|
523
|
-
const fast = String(options.reasoningEffort ?? '') ===
|
|
526
|
+
const fast = String(options.reasoningEffort ?? '') === FAST_RUNG;
|
|
524
527
|
const model = fast ? this.fastVariants.variantOf(base) ?? base : base;
|
|
525
528
|
const call = { ...options, model };
|
|
526
529
|
// xAI has no `fast` effort; the variant already carries the behaviour.
|
|
@@ -1000,7 +1003,7 @@ export function createGrokProvider(options) {
|
|
|
1000
1003
|
const pair = adapter.fastPairOf(id);
|
|
1001
1004
|
const image = adapter.imageModelId();
|
|
1002
1005
|
return {
|
|
1003
|
-
...pair === undefined ? {} : { fast: pair.wireId, fastBase: pair.baseId },
|
|
1006
|
+
...pair === undefined ? {} : { fast: pair.wireId, fastBase: pair.baseId, fastRung: 'lightspeed' },
|
|
1004
1007
|
...image === undefined ? {} : { image },
|
|
1005
1008
|
};
|
|
1006
1009
|
},
|
|
@@ -154,7 +154,7 @@ export function createKimiProvider(options) {
|
|
|
154
154
|
const pair = adapter.fastPairOf(id);
|
|
155
155
|
const image = adapter.imageModelId();
|
|
156
156
|
return {
|
|
157
|
-
...pair === undefined ? {} : { fast: pair.wireId, fastBase: pair.baseId },
|
|
157
|
+
...pair === undefined ? {} : { fast: pair.wireId, fastBase: pair.baseId, fastRung: 'highspeed' },
|
|
158
158
|
...image === undefined ? {} : { image },
|
|
159
159
|
};
|
|
160
160
|
},
|
package/lib/providers/kimi.js
CHANGED
|
@@ -55,6 +55,8 @@ const EFFORT_WIRE_MAP = {
|
|
|
55
55
|
xhigh: 'max',
|
|
56
56
|
max: 'max',
|
|
57
57
|
};
|
|
58
|
+
/** The rung id the composer's quick-mode button selects. */
|
|
59
|
+
const FAST_RUNG = 'highspeed';
|
|
58
60
|
/** Display metadata for the reasoning ladder shared by every live coding model. */
|
|
59
61
|
const EFFORT_LABELS = {
|
|
60
62
|
none: { name: 'Off', description: 'Answer without spending thinking tokens' },
|
|
@@ -65,8 +67,9 @@ const EFFORT_LABELS = {
|
|
|
65
67
|
max: { name: 'Max', description: 'Highest thinking budget Kimi accepts' },
|
|
66
68
|
// Not a thinking budget: this rung is the model's fast variant, which the composer's
|
|
67
69
|
// quick-mode button selects. It rides the effort channel so the row keeps its own id —
|
|
68
|
-
// a swapped model id would leave the picker with no way back
|
|
69
|
-
|
|
70
|
+
// a swapped model id would leave the picker with no way back. Kimi's own word for it
|
|
71
|
+
// is "highspeed", which is what the rung is named here.
|
|
72
|
+
[FAST_RUNG]: { name: 'Highspeed', description: 'Kimi highspeed variant of this model' },
|
|
70
73
|
};
|
|
71
74
|
/**
|
|
72
75
|
* Kimi Code's live coding catalog.
|
|
@@ -128,7 +131,7 @@ function effortInfo(effort) {
|
|
|
128
131
|
*/
|
|
129
132
|
function reasoningInfo(entry, fast = false) {
|
|
130
133
|
const efforts = entry.reasoningEfforts;
|
|
131
|
-
const ids = fast ? [...efforts ?? [],
|
|
134
|
+
const ids = fast ? [...efforts ?? [], FAST_RUNG] : efforts;
|
|
132
135
|
if (ids === undefined || ids.length === 0)
|
|
133
136
|
return undefined;
|
|
134
137
|
const defaultEffort = entry.defaultEffort ?? ids[ids.length - 1];
|
|
@@ -288,7 +291,7 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
288
291
|
// Fast mode rides the reasoning channel: `fast` is a rung of the row, not a model id
|
|
289
292
|
// of its own, so the row the picker selected stays dispatchable and the user can
|
|
290
293
|
// always select the standard model again. Only the wire id changes.
|
|
291
|
-
const fast = String(options.reasoningEffort ?? '') ===
|
|
294
|
+
const fast = String(options.reasoningEffort ?? '') === FAST_RUNG;
|
|
292
295
|
const model = fast ? this.fastVariants.variantOf(options.model) ?? options.model : options.model;
|
|
293
296
|
const call = { ...options, model };
|
|
294
297
|
// Kimi has no `fast` effort; the variant already carries the behaviour.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `mimo` provider route's wiring: credential store, Xiaomi QR/password
|
|
3
|
+
* login, model catalog, and its Settings card facts.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-plugin-subscription-oauth/providers/mimo-provider
|
|
6
|
+
*/
|
|
7
|
+
import type { ProviderRoute } from '../auth/registry.js';
|
|
8
|
+
import type { ModelPreferences } from '../settings.js';
|
|
9
|
+
import type { FetchFn } from '../http.js';
|
|
10
|
+
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
11
|
+
/** Everything the MiMo route needs from the plugin configuration. */
|
|
12
|
+
export interface MimoProviderOptions {
|
|
13
|
+
/** Absolute path of `auth.json`. */
|
|
14
|
+
credentialFile: string;
|
|
15
|
+
/** Abandon a stream after this much silence. */
|
|
16
|
+
streamIdleTimeoutMs: number;
|
|
17
|
+
/** Model the picker preselects. */
|
|
18
|
+
defaultModel?: string;
|
|
19
|
+
/** Shared model-visibility preferences. */
|
|
20
|
+
preferences: ModelPreferences;
|
|
21
|
+
/** Resolve the attachment service per request. */
|
|
22
|
+
resolveAttachments?: () => AttachmentStore | undefined;
|
|
23
|
+
/** Invoked when the catalog or its visibility changed. */
|
|
24
|
+
onCatalogChanged?: () => void;
|
|
25
|
+
onWarn?: (message: string) => void;
|
|
26
|
+
/** Fetch implementation; defaults to the proxy-aware fetch. */
|
|
27
|
+
fetchFn?: FetchFn;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the `mimo` route: its adapter and its login/lifecycle controller.
|
|
31
|
+
*
|
|
32
|
+
* @param options - resolved endpoints, paths, and shared services.
|
|
33
|
+
* @returns the route, ready to register and to serve over RPC.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createMimoProvider(options: MimoProviderOptions): ProviderRoute;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `mimo` provider route's wiring: credential store, Xiaomi QR/password
|
|
3
|
+
* login, model catalog, and its Settings card facts.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-plugin-subscription-oauth/providers/mimo-provider
|
|
6
|
+
*/
|
|
7
|
+
import { REAUTH_REQUIRED, ProviderError } from '../errors.js';
|
|
8
|
+
import { TokenStore } from '../auth/store.js';
|
|
9
|
+
import { TokenManager } from '../auth/tokens.js';
|
|
10
|
+
import { LOGIN_CANCELLED_MESSAGE } from '../auth/device-flow.js';
|
|
11
|
+
import { finishMimoSignIn, mimoCredentialsFrom, refreshMimoSession, startMimoSignIn, waitMimoPoll, } from '../auth/mimo-flow.js';
|
|
12
|
+
import { MimoAdapter, MIMO_CODE_URL, MIMO_CONTEXT_WINDOW, MIMO_DEFAULT_MODEL, MIMO_MODELS, MIMO_PROVIDER_ID, MIMO_PROVIDER_NAME, parseMimoSession, } from './mimo.js';
|
|
13
|
+
import { ProviderController } from '../auth/provider-controller.js';
|
|
14
|
+
import { proxiedFetch } from '../http.js';
|
|
15
|
+
/** Wait, honouring cancellation. */
|
|
16
|
+
function sleep(ms, signal) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
if (signal.aborted) {
|
|
19
|
+
reject(new Error(LOGIN_CANCELLED_MESSAGE));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const timer = setTimeout(() => {
|
|
23
|
+
signal.removeEventListener('abort', onAbort);
|
|
24
|
+
resolve();
|
|
25
|
+
}, ms);
|
|
26
|
+
function onAbort() {
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
reject(new Error(LOGIN_CANCELLED_MESSAGE));
|
|
29
|
+
}
|
|
30
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build the `mimo` route: its adapter and its login/lifecycle controller.
|
|
35
|
+
*
|
|
36
|
+
* @param options - resolved endpoints, paths, and shared services.
|
|
37
|
+
* @returns the route, ready to register and to serve over RPC.
|
|
38
|
+
*/
|
|
39
|
+
export function createMimoProvider(options) {
|
|
40
|
+
const fetchFn = options.fetchFn ?? proxiedFetch;
|
|
41
|
+
const store = new TokenStore(options.credentialFile, MIMO_PROVIDER_ID, parseMimoSession);
|
|
42
|
+
const toSession = (creds, cookies, userId) => ({
|
|
43
|
+
accessToken: JSON.stringify(cookies),
|
|
44
|
+
expiresAt: Date.now() + 24 * 60 * 60 * 1000,
|
|
45
|
+
accountId: userId,
|
|
46
|
+
apiBase: creds.base,
|
|
47
|
+
mimoCreds: JSON.stringify(creds),
|
|
48
|
+
mimoCookies: JSON.stringify(cookies),
|
|
49
|
+
region: creds.region,
|
|
50
|
+
});
|
|
51
|
+
/** The in-flight attempt, so `pending()` is stable across calls. */
|
|
52
|
+
let current;
|
|
53
|
+
const flow = {
|
|
54
|
+
async start() {
|
|
55
|
+
const start = await startMimoSignIn(fetchFn);
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const session = (async () => {
|
|
58
|
+
while (true) {
|
|
59
|
+
if (controller.signal.aborted)
|
|
60
|
+
throw new Error(LOGIN_CANCELLED_MESSAGE);
|
|
61
|
+
const poll = await waitMimoPoll(start, fetchFn);
|
|
62
|
+
if (poll !== undefined) {
|
|
63
|
+
const { creds, cookies } = await finishMimoSignIn(poll, start.device, fetchFn);
|
|
64
|
+
return toSession(creds, cookies, creds.userId);
|
|
65
|
+
}
|
|
66
|
+
await sleep(1000, controller.signal);
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
const attempt = {
|
|
70
|
+
prompt: {
|
|
71
|
+
url: start.url,
|
|
72
|
+
expiresAt: start.until,
|
|
73
|
+
mode: 'open-url',
|
|
74
|
+
},
|
|
75
|
+
session,
|
|
76
|
+
cancel: () => { controller.abort(); },
|
|
77
|
+
};
|
|
78
|
+
current = attempt;
|
|
79
|
+
void session.catch(() => undefined).finally(() => {
|
|
80
|
+
if (current === attempt)
|
|
81
|
+
current = undefined;
|
|
82
|
+
});
|
|
83
|
+
return attempt;
|
|
84
|
+
},
|
|
85
|
+
pending() {
|
|
86
|
+
return current;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const tokens = new TokenManager({
|
|
90
|
+
store,
|
|
91
|
+
displayName: MIMO_PROVIDER_NAME,
|
|
92
|
+
// The app signs on again after a day; renew a little ahead of that.
|
|
93
|
+
preemptMs: 10 * 60 * 1000,
|
|
94
|
+
refresh: async (session) => {
|
|
95
|
+
let creds;
|
|
96
|
+
try {
|
|
97
|
+
creds = JSON.parse(session.mimoCreds);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new ProviderError(`${MIMO_PROVIDER_NAME} sign-in has expired; connect the account again`, REAUTH_REQUIRED);
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const renewed = await refreshMimoSession(creds, fetchFn);
|
|
104
|
+
return { accessToken: JSON.stringify(renewed.cookies), expiresAt: renewed.expiresAt };
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof Error && /lapsed/i.test(error.message)) {
|
|
108
|
+
throw new ProviderError(`${MIMO_PROVIDER_NAME} sign-in has expired; connect the account again`, REAUTH_REQUIRED);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const adapter = new MimoAdapter({
|
|
115
|
+
streamIdleTimeoutMs: options.streamIdleTimeoutMs,
|
|
116
|
+
tokens,
|
|
117
|
+
...options.onWarn === undefined ? {} : { onWarn: options.onWarn },
|
|
118
|
+
...options.resolveAttachments === undefined ? {} : { resolveAttachments: options.resolveAttachments },
|
|
119
|
+
hiddenModels: () => options.preferences.hiddenIds(MIMO_PROVIDER_ID),
|
|
120
|
+
userModels: () => options.preferences.extraIds(MIMO_PROVIDER_ID),
|
|
121
|
+
fetchFn,
|
|
122
|
+
});
|
|
123
|
+
const spec = {
|
|
124
|
+
id: MIMO_PROVIDER_ID,
|
|
125
|
+
displayName: MIMO_PROVIDER_NAME,
|
|
126
|
+
codeUrl: MIMO_CODE_URL,
|
|
127
|
+
credentialFile: options.credentialFile,
|
|
128
|
+
// MiMo ships a fixed two-model catalog; there is no discovery endpoint.
|
|
129
|
+
discovery: false,
|
|
130
|
+
defaultModel: options.defaultModel ?? MIMO_DEFAULT_MODEL,
|
|
131
|
+
catalog: () => adapter.catalogEntries(),
|
|
132
|
+
catalogOwn: () => adapter.ownCatalogEntries(),
|
|
133
|
+
fallbackContextWindow: MIMO_CONTEXT_WINDOW,
|
|
134
|
+
discover: () => Promise.resolve(undefined),
|
|
135
|
+
usage: () => Promise.resolve({ windows: [] }),
|
|
136
|
+
details: () => [
|
|
137
|
+
{ key: 'provider', value: MIMO_PROVIDER_NAME },
|
|
138
|
+
{ key: 'credentialFile', value: options.credentialFile },
|
|
139
|
+
],
|
|
140
|
+
applyDiscovery: () => { },
|
|
141
|
+
};
|
|
142
|
+
const controller = new ProviderController({
|
|
143
|
+
spec,
|
|
144
|
+
store,
|
|
145
|
+
tokens,
|
|
146
|
+
flow,
|
|
147
|
+
preferences: options.preferences,
|
|
148
|
+
...options.onCatalogChanged === undefined ? {} : { onCatalogChanged: options.onCatalogChanged },
|
|
149
|
+
...options.onWarn === undefined ? {} : { onWarn: options.onWarn },
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
id: controller.id,
|
|
153
|
+
displayName: controller.displayName,
|
|
154
|
+
credentialFile: controller.credentialFile,
|
|
155
|
+
adapter,
|
|
156
|
+
status: () => controller.status(),
|
|
157
|
+
login: (account) => controller.login(account),
|
|
158
|
+
cancel: () => controller.cancel(),
|
|
159
|
+
logout: (account) => controller.logout(account),
|
|
160
|
+
setDefault: (account) => controller.setDefault(account),
|
|
161
|
+
refresh: (account) => controller.refresh(account),
|
|
162
|
+
discover: (force) => controller.discover(force),
|
|
163
|
+
setModelVisible: (id, visible) => controller.setModelVisible(id, visible),
|
|
164
|
+
setModelOrder: (ids) => controller.setModelOrder(ids),
|
|
165
|
+
addModel: (id) => controller.addModel(id),
|
|
166
|
+
removeModel: (id) => controller.removeModel(id),
|
|
167
|
+
usage: (force) => controller.usage(force),
|
|
168
|
+
reload: () => controller.reload(),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
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, type GenerateOptions, type LlmModelInfo, type LlmProviderInfo, type LlmResolvedModelInfo, type StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
12
|
+
import { type FetchFn } from '../http.js';
|
|
13
|
+
import type { TokenManager } from '../auth/tokens.js';
|
|
14
|
+
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
15
|
+
/** The `mimo` provider route key. */
|
|
16
|
+
export declare const MIMO_PROVIDER_ID = "mimo";
|
|
17
|
+
/** Human name for the route and its cards. */
|
|
18
|
+
export declare const MIMO_PROVIDER_NAME = "Xiaomi MiMo";
|
|
19
|
+
/** Where the Settings page sends someone to get an account. */
|
|
20
|
+
export declare const MIMO_CODE_URL = "https://platform.xiaomimimo.com";
|
|
21
|
+
/** MiMo context window shared by Pro and Flash. */
|
|
22
|
+
export declare const MIMO_CONTEXT_WINDOW = 1000000;
|
|
23
|
+
/** The output ceiling MiMo advertises. */
|
|
24
|
+
export declare const MIMO_DEFAULT_MAX_TOKENS = 128000;
|
|
25
|
+
/** The model the picker preselects. */
|
|
26
|
+
export declare const MIMO_DEFAULT_MODEL = "mimo-pro";
|
|
27
|
+
/** One signed-in MiMo account, as stored. */
|
|
28
|
+
export interface MimoSession {
|
|
29
|
+
accessToken: string;
|
|
30
|
+
refreshToken?: string;
|
|
31
|
+
expiresAt: number;
|
|
32
|
+
accountId?: string;
|
|
33
|
+
email?: string;
|
|
34
|
+
/** The MiMo server base this account signs on at. */
|
|
35
|
+
apiBase: string;
|
|
36
|
+
/** The durable Xiaomi credential (JSON), used to re-sign-on. */
|
|
37
|
+
mimoCreds: string;
|
|
38
|
+
/** The live session cookies (JSON), sent as the Cookie header. */
|
|
39
|
+
mimoCookies: string;
|
|
40
|
+
/** The account's region. */
|
|
41
|
+
region?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Validate one stored value as a MiMo session, or undefined when it is not one. */
|
|
44
|
+
export declare function parseMimoSession(value: unknown): MimoSession | undefined;
|
|
45
|
+
/** One catalog row. */
|
|
46
|
+
interface MimoModelEntry {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
description: string;
|
|
50
|
+
contextWindow: number;
|
|
51
|
+
}
|
|
52
|
+
/** The two models the MiMo app serves. */
|
|
53
|
+
export declare const MIMO_MODELS: readonly MimoModelEntry[];
|
|
54
|
+
/** Constructor dependencies for {@link MimoAdapter}. */
|
|
55
|
+
export interface MimoAdapterOptions {
|
|
56
|
+
/** MiMo server base; `<base>/route/chat/completions` is the endpoint. */
|
|
57
|
+
apiBase?: string;
|
|
58
|
+
streamIdleTimeoutMs: number;
|
|
59
|
+
tokens: TokenManager<MimoSession>;
|
|
60
|
+
onWarn?: (message: string) => void;
|
|
61
|
+
resolveAttachments?: () => AttachmentStore | undefined;
|
|
62
|
+
hiddenModels?: () => Promise<ReadonlySet<string>>;
|
|
63
|
+
userModels?: () => Promise<readonly string[]>;
|
|
64
|
+
fetchFn?: FetchFn;
|
|
65
|
+
}
|
|
66
|
+
/** The MiMo wire adapter serving the `mimo` route. */
|
|
67
|
+
export declare class MimoAdapter extends LlmAdapter {
|
|
68
|
+
private readonly options;
|
|
69
|
+
private readonly fetchFn;
|
|
70
|
+
constructor(options: MimoAdapterOptions);
|
|
71
|
+
providerInfo(provider: string): LlmProviderInfo;
|
|
72
|
+
listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
|
|
73
|
+
private hidden;
|
|
74
|
+
resolveModel(_provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
75
|
+
providerRetryPolicy(): undefined;
|
|
76
|
+
/** The catalog the Settings list reads: every shipped model. */
|
|
77
|
+
catalogEntries(): readonly {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
contextWindow: number;
|
|
81
|
+
}[];
|
|
82
|
+
/** The ids the route itself serves, without hand-added extras. */
|
|
83
|
+
ownCatalogEntries(): readonly MimoModelEntry[];
|
|
84
|
+
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
85
|
+
/**
|
|
86
|
+
* The session cookies as one Cookie header, in a steady order.
|
|
87
|
+
*
|
|
88
|
+
* MiMo is a cookie session, not a bearer: the wire credential is the cookie
|
|
89
|
+
* header built from the stored session's cookie record.
|
|
90
|
+
*/
|
|
91
|
+
private cookieHeader;
|
|
92
|
+
private requestBody;
|
|
93
|
+
private toChatMessage;
|
|
94
|
+
private readStream;
|
|
95
|
+
}
|
|
96
|
+
export {};
|