dsh-plugin-subscription-oauth 0.16.3 → 0.17.1
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.dev.md +1 -1
- package/README.md +7 -6
- package/README.zh.dev.md +1 -1
- package/README.zh.md +7 -6
- package/lib/client/FastModeButton.d.ts +0 -1
- package/lib/client/FastModeButton.js +48 -11
- package/lib/client.js +32 -9
- package/lib/client.js.map +1 -1
- package/lib/index.js +21 -0
- package/lib/protocol.d.ts +9 -0
- package/lib/providers/antigravity.d.ts +34 -4
- package/lib/providers/antigravity.js +100 -28
- package/lib/providers/ark-provider.d.ts +10 -0
- package/lib/providers/ark-provider.js +16 -2
- package/lib/providers/ark.d.ts +104 -10
- package/lib/providers/ark.js +186 -48
- package/lib/providers/catalog.d.ts +92 -0
- package/lib/providers/catalog.js +191 -0
- package/lib/providers/common.d.ts +14 -0
- package/lib/providers/common.js +23 -0
- package/lib/providers/glm-provider.d.ts +10 -0
- package/lib/providers/glm-provider.js +16 -2
- package/lib/providers/glm.d.ts +100 -16
- package/lib/providers/glm.js +182 -85
- package/lib/providers/grok.d.ts +114 -27
- package/lib/providers/grok.js +330 -129
- package/lib/providers/kimi-provider.d.ts +10 -0
- package/lib/providers/kimi-provider.js +16 -4
- package/lib/providers/kimi.d.ts +122 -32
- package/lib/providers/kimi.js +243 -122
- package/lib/providers/mimo-provider.d.ts +14 -1
- package/lib/providers/mimo-provider.js +17 -6
- package/lib/providers/mimo.d.ts +164 -19
- package/lib/providers/mimo.js +318 -19
- package/lib/providers/model-cache.d.ts +57 -0
- package/lib/providers/model-cache.js +105 -0
- package/lib/providers/responses-stream.d.ts +8 -0
- package/lib/providers/responses-stream.js +21 -5
- package/lib/providers/workbuddy-provider.d.ts +17 -0
- package/lib/providers/workbuddy-provider.js +14 -6
- package/lib/providers/workbuddy.d.ts +140 -13
- package/lib/providers/workbuddy.js +501 -59
- package/package.json +1 -1
package/lib/providers/kimi.js
CHANGED
|
@@ -12,7 +12,7 @@ import { REAUTH_REQUIRED, ProviderError } from '../errors.js';
|
|
|
12
12
|
import { finiteInstant, nonEmpty } from '../auth/store.js';
|
|
13
13
|
import { idleWatchdog, httpLlmError, isMissingOrInvalidCredential, mapFetchFailure } from './common.js';
|
|
14
14
|
import { streamProviderResponses, accountPoolHooks } from './responses-stream.js';
|
|
15
|
-
import { fallbackEntry, findEntry, resolveCatalog } from './catalog.js';
|
|
15
|
+
import { composeCatalog, fallbackEntry, findEntry, liveEntry, liveModelFacts, resolveCatalog } from './catalog.js';
|
|
16
16
|
import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
17
17
|
import { prepareMoonshotTool } from '../translate/moonshot-schema.js';
|
|
18
18
|
import { isImageModel, isFastVariant, baseModelOf, fastWordOf, FastVariantIndex } from './grok.js';
|
|
@@ -72,52 +72,45 @@ const EFFORT_LABELS = {
|
|
|
72
72
|
[FAST_RUNG]: { name: 'Highspeed', description: 'Kimi highspeed variant of this model' },
|
|
73
73
|
};
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Stable labels for the ids this route is known for.
|
|
76
76
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
77
|
+
* Labels, not a roster: nothing here decides which models exist or what they serve —
|
|
78
|
+
* membership, windows, ladders and modalities are the endpoint's listing. What it fixes is
|
|
79
|
+
* the one thing the listing cannot answer, because the id does not name it: `kimi-for-coding`
|
|
80
|
+
* is the subscription alias the service re-points at each coding release, so its
|
|
81
|
+
* `display_name` names the release of the moment (`K2.8 Preview` the week K2.8 ships) while
|
|
82
|
+
* the id the user selected stays the same. Labelling that row by release would rename the
|
|
83
|
+
* user's saved selection overnight, so the alias keeps its own name — and the endpoint's
|
|
84
|
+
* release name is deliberately not shown, because the row is the alias, not the version.
|
|
85
|
+
*
|
|
86
|
+
* A model the endpoint adds that this map does not name is published exactly as the
|
|
87
|
+
* endpoint describes it.
|
|
80
88
|
*/
|
|
81
|
-
|
|
82
|
-
{
|
|
83
|
-
id: 'kimi-for-coding',
|
|
89
|
+
const STABLE_MODEL_LABELS = {
|
|
90
|
+
'kimi-for-coding': {
|
|
84
91
|
name: 'Kimi for Coding',
|
|
85
|
-
description: '
|
|
86
|
-
aliases: ['kimi-for-coding-highspeed', 'kimi-k2.7-code', 'kimi-k2.7-code-highspeed', 'kimi-k2.6', 'kimi-k2.5'],
|
|
87
|
-
contextWindow: KIMI_LONG_CONTEXT_WINDOW,
|
|
88
|
-
inputModalities: ['text', 'image'],
|
|
89
|
-
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
90
|
-
defaultEffort: 'max',
|
|
92
|
+
description: 'Subscription alias — the endpoint re-points it at the current coding release',
|
|
91
93
|
},
|
|
92
|
-
{
|
|
93
|
-
id: 'k3',
|
|
94
|
+
'k3': {
|
|
94
95
|
name: 'Kimi K3',
|
|
95
96
|
description: 'Flagship reasoning model',
|
|
96
|
-
aliases: ['kimi-k3'],
|
|
97
|
-
contextWindow: KIMI_CONTEXT_WINDOW,
|
|
98
|
-
inputModalities: ['text', 'image'],
|
|
99
|
-
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
100
|
-
defaultEffort: 'max',
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: 'k3[1m]',
|
|
104
|
-
name: 'Kimi K3 (1M)',
|
|
105
|
-
description: 'Flagship reasoning model with the 1M-token window',
|
|
106
|
-
contextWindow: KIMI_LONG_CONTEXT_WINDOW,
|
|
107
|
-
inputModalities: ['text', 'image'],
|
|
108
|
-
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
109
|
-
defaultEffort: 'max',
|
|
110
97
|
},
|
|
111
|
-
{
|
|
112
|
-
id: 'k3-256k',
|
|
98
|
+
'k3-256k': {
|
|
113
99
|
name: 'Kimi K3 (256K)',
|
|
114
|
-
description: 'Flagship reasoning model pinned to the
|
|
115
|
-
contextWindow: KIMI_CONTEXT_WINDOW,
|
|
116
|
-
inputModalities: ['text', 'image'],
|
|
117
|
-
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
118
|
-
defaultEffort: 'max',
|
|
100
|
+
description: 'Flagship reasoning model pinned to the 256K-token window',
|
|
119
101
|
},
|
|
120
|
-
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Spellings the endpoint has retired, and the model each one became.
|
|
105
|
+
*
|
|
106
|
+
* A repair table, not a roster: nothing here decides which models exist. Each entry
|
|
107
|
+
* is consulted only for an id the live listing does not itself serve, so a spelling
|
|
108
|
+
* the endpoint reintroduces is dispatched exactly as the endpoint names it. The
|
|
109
|
+
* endpoint's own 401 message (`Please set model id as \`k3\``) is the general case
|
|
110
|
+
* and needs no table at all; this only saves the first failed round trip for a
|
|
111
|
+
* spelling already known to be gone.
|
|
112
|
+
*/
|
|
113
|
+
const RETIRED_MODEL_IDS = { 'k3[1m]': 'k3' };
|
|
121
114
|
/** One `reasoning.efforts` entry for a resolved model. */
|
|
122
115
|
function effortInfo(effort) {
|
|
123
116
|
const label = EFFORT_LABELS[effort] ?? { name: effort, description: '' };
|
|
@@ -145,48 +138,132 @@ function reasoningInfo(entry, fastWireId) {
|
|
|
145
138
|
/** Kimi Code adapter serving the `kimi` provider route. */
|
|
146
139
|
export class KimiAdapter extends LlmAdapter {
|
|
147
140
|
options;
|
|
148
|
-
/**
|
|
149
|
-
|
|
150
|
-
|
|
141
|
+
/**
|
|
142
|
+
* The catalog this route serves: entries derived from the endpoint's own listing,
|
|
143
|
+
* either the one just read or the one the cache kept from the last read.
|
|
144
|
+
*
|
|
145
|
+
* Undefined means neither source has produced a listing yet, so only the configured
|
|
146
|
+
* default model is offered until one does.
|
|
147
|
+
*/
|
|
148
|
+
live;
|
|
149
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
150
|
+
hydrated = false;
|
|
151
|
+
/** Image-generation model id, when the listing reported one. */
|
|
151
152
|
imageModel;
|
|
152
153
|
/**
|
|
153
154
|
* Fast-variant wire id per picker row, for the composer's quick-mode button.
|
|
154
155
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* the
|
|
156
|
+
* Derived from the listing: `kimi-for-coding-highspeed` is a row of its own there,
|
|
157
|
+
* and pairs with `kimi-for-coding` because its name is that of a listed model plus
|
|
158
|
+
* the provider's own fast word.
|
|
158
159
|
*/
|
|
159
160
|
fastVariants = new FastVariantIndex();
|
|
160
161
|
/** Ids the user added by hand, cached so the sync catalog can name them. */
|
|
161
162
|
userAddedIds = [];
|
|
163
|
+
/**
|
|
164
|
+
* Model ids the endpoint renamed during this session, and what each became.
|
|
165
|
+
*
|
|
166
|
+
* Learned from the endpoint's own refusal (`Please set model id as \`k3\``), so a
|
|
167
|
+
* spelling retired after this build shipped is repaired without a plugin update. The
|
|
168
|
+
* first turn pays one refused request; every later turn goes straight to the current id.
|
|
169
|
+
*/
|
|
170
|
+
renamed = new Map();
|
|
162
171
|
/** Tools already reported as flattened, so each is stated once. */
|
|
163
172
|
flattenedTools = new Set();
|
|
164
173
|
constructor(options) {
|
|
165
174
|
super();
|
|
166
175
|
this.options = options;
|
|
167
|
-
this.seedCuratedFastVariants();
|
|
168
176
|
}
|
|
169
177
|
/**
|
|
170
|
-
*
|
|
178
|
+
* Adopt the rows of one live listing as this route's catalog.
|
|
171
179
|
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
180
|
+
* Everything the picker shows is derived here from the endpoint's own answer: the
|
|
181
|
+
* rows that become picker entries, the fast variant that pairs with each row, and
|
|
182
|
+
* the image model that is called by an agent rather than chosen. Nothing about
|
|
183
|
+
* which models exist, or what each one is, is decided by this build.
|
|
184
|
+
*
|
|
185
|
+
* Fast variants ride with their base row instead of becoming rows of their own: they
|
|
186
|
+
* share the row's visibility switch and are reached by the composer's quick-mode
|
|
187
|
+
* button. A variant is recognized by its own name — the provider's fast word appended
|
|
188
|
+
* to a listed model's id — so no curated alias map is needed to pair them.
|
|
189
|
+
*
|
|
190
|
+
* @param rows - decoded rows of the endpoint's listing, in the endpoint's order.
|
|
191
|
+
* @returns the entries to publish, in picker order.
|
|
176
192
|
*/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
adoptListing(rows) {
|
|
194
|
+
const facts = rows
|
|
195
|
+
.map((row) => liveModelFacts(row))
|
|
196
|
+
.filter((fact) => fact !== undefined);
|
|
197
|
+
// Image models are never picker rows: they are agent-called, always on.
|
|
198
|
+
this.imageModel = facts.find((fact) => isImageModel(fact.id))?.id;
|
|
199
|
+
this.fastVariants.clear();
|
|
200
|
+
for (const fact of facts)
|
|
201
|
+
this.fastVariants.noteListed(fact.id);
|
|
202
|
+
for (const fact of facts) {
|
|
203
|
+
const base = this.fastVariants.pair(fact.id);
|
|
204
|
+
this.fastVariants.note(fact.id, () => base ?? baseModelOf(fact.id));
|
|
205
|
+
}
|
|
206
|
+
// Fold a variant onto its row and drop the ids that are not chat models, keeping the
|
|
207
|
+
// endpoint's order. A row's own facts describe the row; a variant's describe the
|
|
208
|
+
// variant, so a row that is also listed is never overwritten by its variant.
|
|
209
|
+
const byId = new Map();
|
|
210
|
+
for (const fact of facts) {
|
|
211
|
+
if (NON_CHAT_ID.test(fact.id))
|
|
212
|
+
continue;
|
|
213
|
+
const id = isFastVariant(fact.id) ? this.fastVariants.pair(fact.id) ?? baseModelOf(fact.id) : fact.id;
|
|
214
|
+
if (id === fact.id || !byId.has(id))
|
|
215
|
+
byId.set(id, { ...fact, id });
|
|
216
|
+
}
|
|
217
|
+
return [...byId.values()].map((fact) => this.labelled(liveEntry(fact, [], KIMI_CONTEXT_WINDOW, 'Reported by the Kimi Code model list')));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Put this route's stable label on one entry, when the id has one.
|
|
221
|
+
*
|
|
222
|
+
* The listing states what the model *is*; the label states what the *id* is, which only
|
|
223
|
+
* this route knows (see {@link STABLE_MODEL_LABELS}).
|
|
224
|
+
*
|
|
225
|
+
* @param entry - the entry built from the listing.
|
|
226
|
+
* @returns the entry, relabelled when its id has a stable label.
|
|
227
|
+
*/
|
|
228
|
+
labelled(entry) {
|
|
229
|
+
const stable = STABLE_MODEL_LABELS[entry.id];
|
|
230
|
+
return stable === undefined ? entry : { ...entry, name: stable.name, description: stable.description };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
234
|
+
*
|
|
235
|
+
* Called before the first catalog query. A read failure is a cache miss, never a
|
|
236
|
+
* failure: the route simply has no listing until one is fetched.
|
|
237
|
+
*/
|
|
238
|
+
async hydrate() {
|
|
239
|
+
if (this.hydrated)
|
|
240
|
+
return;
|
|
241
|
+
this.hydrated = true;
|
|
242
|
+
if (this.live !== undefined || this.options.cachedListing === undefined)
|
|
243
|
+
return;
|
|
244
|
+
try {
|
|
245
|
+
const rows = await this.options.cachedListing();
|
|
246
|
+
if (Array.isArray(rows) && rows.length > 0)
|
|
247
|
+
this.live = this.adoptListing(rows);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
this.options.onWarn?.(`cached model catalog unavailable (${errorChain(error)})`);
|
|
184
251
|
}
|
|
185
252
|
}
|
|
186
253
|
/** The fast pair one picker id belongs to, from either side. */
|
|
187
254
|
fastPairOf(id) {
|
|
188
255
|
return this.fastVariants.pairOf(id);
|
|
189
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Whether this route has read a model listing yet.
|
|
259
|
+
*
|
|
260
|
+
* A variant is derived from the listing, so until one has been read "this model has no
|
|
261
|
+
* variant" and "the listing has not arrived" look identical from outside; a caller that
|
|
262
|
+
* asks once (the composer's quick-mode button) needs to tell them apart.
|
|
263
|
+
*/
|
|
264
|
+
hasListing() {
|
|
265
|
+
return this.live !== undefined;
|
|
266
|
+
}
|
|
190
267
|
/**
|
|
191
268
|
* Tool schemas in the shape Moonshot accepts: no `$ref`, and under its size
|
|
192
269
|
* ceiling. A declaration too large to send keeps its arguments, and that is
|
|
@@ -219,6 +296,7 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
219
296
|
// must not appear in the picker, and Auto must not try it.
|
|
220
297
|
if (await this.options.tokens.session() === undefined)
|
|
221
298
|
return [];
|
|
299
|
+
await this.hydrate();
|
|
222
300
|
await this.syncUserModels();
|
|
223
301
|
const hidden = await this.hidden();
|
|
224
302
|
// One row per model, always under the model's own id: the host validates every GUI
|
|
@@ -226,8 +304,7 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
226
304
|
// picker with no way back to the standard model. Fast mode is the row's `fast`
|
|
227
305
|
// reasoning rung instead (see `stream`).
|
|
228
306
|
const imageless = this.catalog().filter((entry) => !isImageModel(entry.id));
|
|
229
|
-
return resolveCatalog(imageless, [], hidden).entries.map((entry) => ({
|
|
230
|
-
provider: KIMI_PROVIDER_ID,
|
|
307
|
+
return resolveCatalog(imageless, [], hidden).entries.map((entry) => ({ provider: KIMI_PROVIDER_ID,
|
|
231
308
|
id: entry.id,
|
|
232
309
|
name: entry.name,
|
|
233
310
|
...entry.description === undefined ? {} : { description: entry.description },
|
|
@@ -256,8 +333,12 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
256
333
|
}
|
|
257
334
|
}
|
|
258
335
|
async resolveModel(_provider, model) {
|
|
336
|
+
await this.hydrate();
|
|
259
337
|
await this.syncUserModels();
|
|
260
|
-
|
|
338
|
+
// A retired spelling answers with the model that replaced it, so a session that
|
|
339
|
+
// saved the old id shows the right name, window and ladder before it dispatches.
|
|
340
|
+
const repaired = this.repairModelId(model);
|
|
341
|
+
const entry = this.entryFor(repaired ?? model);
|
|
261
342
|
if (entry === undefined) {
|
|
262
343
|
// An unknown id is still dispatchable: the endpoint accepts the id it
|
|
263
344
|
// reports, and a model added after this build should not be refused. It
|
|
@@ -277,8 +358,9 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
277
358
|
// picker shows the row under its own name and the mode as the effort.
|
|
278
359
|
const fastWireId = this.fastVariants.variantOf(entry.id);
|
|
279
360
|
const reasoning = reasoningInfo(entry, fastWireId);
|
|
280
|
-
//
|
|
281
|
-
//
|
|
361
|
+
// The listing's own id is what resolves here — a renamed or retired spelling is
|
|
362
|
+
// reported as the model the endpoint now calls it — which is what keeps a selection
|
|
363
|
+
// dispatchable and the picker able to show it.
|
|
282
364
|
return {
|
|
283
365
|
provider: KIMI_PROVIDER_ID,
|
|
284
366
|
id: entry.id,
|
|
@@ -294,10 +376,18 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
294
376
|
// Fast mode rides the reasoning channel: the rung is the variant's own fast word, not
|
|
295
377
|
// a model id of its own, so the row the picker selected stays dispatchable and the user
|
|
296
378
|
// can always select the standard model again. Only the wire id changes.
|
|
297
|
-
const fastWireId = this.fastVariants.variantOf(options.model);
|
|
298
379
|
const effort = String(options.reasoningEffort ?? '');
|
|
380
|
+
// A request that already names a fast variant keeps that wire id: it is a model id of
|
|
381
|
+
// its own rather than a spelling of the row. Every other id is sent exactly as the
|
|
382
|
+
// endpoint's listing names it, except a spelling the endpoint has retired, which goes
|
|
383
|
+
// out as the model that replaced it — a session that saved `k3[1m]` repairs itself on
|
|
384
|
+
// the next turn instead of failing with HTTP 401.
|
|
385
|
+
const selected = isFastVariant(options.model)
|
|
386
|
+
? options.model
|
|
387
|
+
: this.repairModelId(options.model) ?? options.model;
|
|
388
|
+
const fastWireId = this.fastVariants.variantOf(selected);
|
|
299
389
|
const fast = fastWireId !== undefined && effort === fastWordOf(fastWireId);
|
|
300
|
-
const model = fast ? fastWireId :
|
|
390
|
+
const model = fast ? fastWireId : selected;
|
|
301
391
|
const call = { ...options, model };
|
|
302
392
|
// Kimi has no fast-variant effort; the variant already carries the behaviour.
|
|
303
393
|
if (fast)
|
|
@@ -314,6 +404,12 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
314
404
|
onAccountSwitch: (from, to, reason) => {
|
|
315
405
|
this.options.onWarn?.(`kimi: account ${from} allowance spent (${reason}); switching to ${to}`);
|
|
316
406
|
},
|
|
407
|
+
// The endpoint names the model id to use when a request asks for one it has
|
|
408
|
+
// retired; remembering it repairs this session rather than failing the turn.
|
|
409
|
+
onModelRenamed: (from, to) => {
|
|
410
|
+
this.renamed.set(from, to);
|
|
411
|
+
this.options.onWarn?.(`kimi: model ${from} is retired; using ${to}`);
|
|
412
|
+
},
|
|
317
413
|
...this.options.onCredentialRejected === undefined
|
|
318
414
|
? {}
|
|
319
415
|
: { onCredentialRejected: this.options.onCredentialRejected },
|
|
@@ -339,36 +435,48 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
339
435
|
return [...this.shippedCatalog(), ...this.addedEntries()];
|
|
340
436
|
}
|
|
341
437
|
/**
|
|
342
|
-
* The catalog to present: the
|
|
343
|
-
*
|
|
344
|
-
* build does not know.
|
|
438
|
+
* The catalog to present: the models the endpoint's listing named, in its own order,
|
|
439
|
+
* each with the metadata that listing stated, then the configured extras.
|
|
345
440
|
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
441
|
+
* There is no shipped roster to fall back to. A listing is read once and cached (see
|
|
442
|
+
* {@link hydrate} and {@link fetchCatalog}), and that cached table is what the picker
|
|
443
|
+
* shows until the next read, so a model released, renamed or retired server-side is
|
|
444
|
+
* reflected without a plugin update or a request per refresh.
|
|
349
445
|
*/
|
|
350
446
|
shippedCatalog() {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
};
|
|
359
|
-
for (const id of this.options.extraModels ?? [])
|
|
360
|
-
append(id, 'Extra model from the plugin configuration');
|
|
361
|
-
for (const id of this.discovered ?? [])
|
|
362
|
-
append(id, 'Reported by the Kimi Code model list');
|
|
363
|
-
return extra.length === 0 ? KIMI_MODELS : [...KIMI_MODELS, ...extra];
|
|
364
|
-
}
|
|
365
|
-
/** Whether one shipped entry covers a live model id. */
|
|
366
|
-
entryMatches(entry, id) {
|
|
367
|
-
return entry.id === id || entry.aliases?.includes(id) === true;
|
|
447
|
+
return composeCatalog({
|
|
448
|
+
curated: [],
|
|
449
|
+
...this.live === undefined ? {} : { live: this.live },
|
|
450
|
+
...this.options.extraModels === undefined ? {} : { extra: this.options.extraModels },
|
|
451
|
+
defaultModel: this.options.defaultModel ?? KIMI_DEFAULT_MODEL,
|
|
452
|
+
fallbackContextWindow: KIMI_CONTEXT_WINDOW,
|
|
453
|
+
extraDescription: 'Extra model from the plugin configuration',
|
|
454
|
+
});
|
|
368
455
|
}
|
|
369
|
-
/** Resolve a requested model id onto
|
|
456
|
+
/** Resolve a requested model id onto the entry the endpoint's listing described. */
|
|
370
457
|
entryFor(model) {
|
|
371
|
-
return findEntry(
|
|
458
|
+
return findEntry(this.live ?? [], model);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* The model one wire id names, when the endpoint has retired the spelling.
|
|
462
|
+
*
|
|
463
|
+
* Consulted only for an id the current listing does not serve, so an id the endpoint
|
|
464
|
+
* itself reports is always dispatched exactly as reported.
|
|
465
|
+
*
|
|
466
|
+
* @param model - the requested model id.
|
|
467
|
+
* @returns the current spelling, or undefined when the id is not a known retirement.
|
|
468
|
+
*/
|
|
469
|
+
repairModelId(model) {
|
|
470
|
+
if (this.entryFor(model) !== undefined)
|
|
471
|
+
return undefined;
|
|
472
|
+
// A fast variant is a wire id rather than a row: a session that saved one belongs on
|
|
473
|
+
// the row that owns it, where the quick-mode rung is what switches the variant on.
|
|
474
|
+
const pair = this.fastVariants.pairOf(model);
|
|
475
|
+
if (pair !== undefined)
|
|
476
|
+
return pair.baseId;
|
|
477
|
+
// Prefer what this session was actually told over the table this build shipped: the
|
|
478
|
+
// endpoint's own answer is newer than any release.
|
|
479
|
+
return this.renamed.get(model) ?? RETIRED_MODEL_IDS[model];
|
|
372
480
|
}
|
|
373
481
|
/** Re-read the ids the user added by hand. */
|
|
374
482
|
async syncUserModels() {
|
|
@@ -396,11 +504,22 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
396
504
|
.filter((id) => !known.has(id))
|
|
397
505
|
.map((id) => fallbackEntry(id, 'Added by hand in Settings', KIMI_CONTEXT_WINDOW));
|
|
398
506
|
}
|
|
399
|
-
/**
|
|
507
|
+
/**
|
|
508
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
509
|
+
*
|
|
510
|
+
* {@link fetchCatalog} already derived the entries from the rows it read, and the
|
|
511
|
+
* controller reports those same ids back here; the entries it recorded are kept. A
|
|
512
|
+
* caller that only has ids (a test, another discovery path) gets conservative entries
|
|
513
|
+
* for them.
|
|
514
|
+
*
|
|
515
|
+
* @param ids - model ids the caller reports.
|
|
516
|
+
*/
|
|
400
517
|
discover(ids) {
|
|
401
|
-
this.
|
|
518
|
+
const known = new Map((this.live ?? []).map((entry) => [entry.id, entry]));
|
|
519
|
+
this.live = ids.map((id) => known.get(id)
|
|
520
|
+
?? this.labelled(liveEntry({ id }, [], KIMI_CONTEXT_WINDOW, 'Reported by the Kimi Code model list')));
|
|
402
521
|
}
|
|
403
|
-
/** The
|
|
522
|
+
/** The catalog, for the Settings page's model list. */
|
|
404
523
|
catalogEntries() {
|
|
405
524
|
return this.catalog();
|
|
406
525
|
}
|
|
@@ -476,7 +595,20 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
476
595
|
}
|
|
477
596
|
};
|
|
478
597
|
}
|
|
479
|
-
/**
|
|
598
|
+
/**
|
|
599
|
+
* Read the endpoint's model listing and adopt it as this route's catalog.
|
|
600
|
+
*
|
|
601
|
+
* This is the one place a model table is produced, and it is the endpoint's own
|
|
602
|
+
* answer: what the listing names is what the picker offers, with the display name,
|
|
603
|
+
* context window, modalities and reasoning ladder the listing states. The rows are
|
|
604
|
+
* also handed to {@link KimiAdapterOptions.saveListing}, so the next session starts
|
|
605
|
+
* from this table instead of asking again — the Settings "refresh models" action is
|
|
606
|
+
* what re-reads it.
|
|
607
|
+
*
|
|
608
|
+
* @param accessToken - bearer for the account.
|
|
609
|
+
* @param signal - optional cancellation.
|
|
610
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
611
|
+
*/
|
|
480
612
|
async fetchCatalog(accessToken, signal) {
|
|
481
613
|
if (!this.options.discovery)
|
|
482
614
|
return undefined;
|
|
@@ -488,45 +620,34 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
488
620
|
});
|
|
489
621
|
if (!response.ok) {
|
|
490
622
|
if (!isMissingOrInvalidCredential(await httpLlmError(response, 'Kimi models'))) {
|
|
491
|
-
this.options.onWarn?.(`Kimi model discovery failed (HTTP ${response.status});
|
|
623
|
+
this.options.onWarn?.(`Kimi model discovery failed (HTTP ${response.status}); keeping the last catalog`);
|
|
492
624
|
}
|
|
493
625
|
return undefined;
|
|
494
626
|
}
|
|
495
627
|
const payload = await response.json();
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
this.imageModel = allIds.find((id) => isImageModel(id));
|
|
501
|
-
// Fast/lightspeed variants ride with their base row: they share the row's
|
|
502
|
-
// visibility switch and are reached by the composer's quick-mode button, not a
|
|
503
|
-
// separate row. The live list is the authority on what this account serves, so
|
|
504
|
-
// it is recorded first, and each variant pairs with the longest listed prefix
|
|
505
|
-
// of its own name — a provider that names its variants after the base model
|
|
506
|
-
// pairs them with no curated map at all.
|
|
507
|
-
this.fastVariants.clear();
|
|
508
|
-
for (const id of allIds) {
|
|
509
|
-
this.fastVariants.noteListed(id);
|
|
510
|
-
}
|
|
511
|
-
for (const id of allIds) {
|
|
512
|
-
const base = this.fastVariants.pair(id);
|
|
513
|
-
this.fastVariants.note(id, () => base ?? this.entryFor(baseModelOf(id))?.id ?? baseModelOf(id));
|
|
514
|
-
}
|
|
515
|
-
this.seedCuratedFastVariants();
|
|
516
|
-
const ids = allIds
|
|
517
|
-
.filter((id) => !NON_CHAT_ID.test(id))
|
|
518
|
-
.map((id) => (isFastVariant(id) ? this.fastVariants.pair(id) ?? baseModelOf(id) : id))
|
|
519
|
-
.filter((id, index, list) => list.indexOf(id) === index);
|
|
520
|
-
if (ids.length === 0) {
|
|
521
|
-
this.options.onWarn?.('Kimi model discovery returned an empty catalog; using the built-in catalog');
|
|
628
|
+
const rows = Array.isArray(payload.data) ? payload.data : [];
|
|
629
|
+
const entries = this.adoptListing(rows);
|
|
630
|
+
if (entries.length === 0) {
|
|
631
|
+
this.options.onWarn?.('Kimi model discovery returned an empty catalog; keeping the last catalog');
|
|
522
632
|
return undefined;
|
|
523
633
|
}
|
|
524
|
-
|
|
634
|
+
this.live = entries;
|
|
635
|
+
// The cached table is the endpoint's answer, kept so the next session does not ask
|
|
636
|
+
// again. A cache failure never fails the read that produced it.
|
|
637
|
+
if (this.options.saveListing !== undefined) {
|
|
638
|
+
try {
|
|
639
|
+
await this.options.saveListing(rows);
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
this.options.onWarn?.(`model catalog cache write failed (${errorChain(error)})`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return entries.map((entry) => entry.id);
|
|
525
646
|
}
|
|
526
647
|
catch (error) {
|
|
527
648
|
if (signal?.aborted === true)
|
|
528
649
|
return undefined;
|
|
529
|
-
this.options.onWarn?.(`Kimi model discovery failed;
|
|
650
|
+
this.options.onWarn?.(`Kimi model discovery failed; keeping the last catalog (${errorChain(error)})`);
|
|
530
651
|
return undefined;
|
|
531
652
|
}
|
|
532
653
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `mimo` provider route's wiring: credential store, Xiaomi QR/password
|
|
3
|
-
* login, model catalog, and
|
|
3
|
+
* login, model catalog (MiMo's own listing, read once and cached), and its
|
|
4
|
+
* Settings card facts.
|
|
4
5
|
*
|
|
5
6
|
* @module dsh-plugin-subscription-oauth/providers/mimo-provider
|
|
6
7
|
*/
|
|
@@ -12,10 +13,22 @@ import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
|
12
13
|
export interface MimoProviderOptions {
|
|
13
14
|
/** Absolute path of `auth.json`. */
|
|
14
15
|
credentialFile: string;
|
|
16
|
+
/** Consult MiMo's own live model listing. Defaults to on. */
|
|
17
|
+
discovery?: boolean;
|
|
15
18
|
/** Abandon a stream after this much silence. */
|
|
16
19
|
streamIdleTimeoutMs: number;
|
|
17
20
|
/** Model the picker preselects. */
|
|
18
21
|
defaultModel?: string;
|
|
22
|
+
/**
|
|
23
|
+
* MiMo's last live listing, from this plugin's cache.
|
|
24
|
+
*
|
|
25
|
+
* The cached table is what the picker shows before any request: the endpoint's own
|
|
26
|
+
* answer from the last read, so a release does not need a plugin update and a refresh
|
|
27
|
+
* does not need a request. Absent, the built-in roster is what the picker shows.
|
|
28
|
+
*/
|
|
29
|
+
cachedListing?: () => Promise<unknown>;
|
|
30
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
31
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
19
32
|
/** Shared model-visibility preferences. */
|
|
20
33
|
preferences: ModelPreferences;
|
|
21
34
|
/** Resolve the attachment service per request. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `mimo` provider route's wiring: credential store, Xiaomi QR/password
|
|
3
|
-
* login, model catalog, and
|
|
3
|
+
* login, model catalog (MiMo's own listing, read once and cached), and its
|
|
4
|
+
* Settings card facts.
|
|
4
5
|
*
|
|
5
6
|
* @module dsh-plugin-subscription-oauth/providers/mimo-provider
|
|
6
7
|
*/
|
|
@@ -9,7 +10,7 @@ import { TokenStore } from '../auth/store.js';
|
|
|
9
10
|
import { TokenManager } from '../auth/tokens.js';
|
|
10
11
|
import { LOGIN_CANCELLED_MESSAGE } from '../auth/device-flow.js';
|
|
11
12
|
import { finishMimoSignIn, mimoCredentialsFrom, readMimoUsage, refreshMimoSession, startMimoSignIn, waitMimoPoll, } from '../auth/mimo-flow.js';
|
|
12
|
-
import { MimoAdapter, MIMO_CODE_URL, MIMO_CONTEXT_WINDOW, MIMO_DEFAULT_MODEL,
|
|
13
|
+
import { MimoAdapter, MIMO_CODE_URL, MIMO_CONTEXT_WINDOW, MIMO_DEFAULT_MODEL, MIMO_PROVIDER_ID, MIMO_PROVIDER_NAME, parseMimoSession, } from './mimo.js';
|
|
13
14
|
import { ProviderController } from '../auth/provider-controller.js';
|
|
14
15
|
import { proxiedFetch } from '../http.js';
|
|
15
16
|
/** Wait, honouring cancellation. */
|
|
@@ -111,27 +112,37 @@ export function createMimoProvider(options) {
|
|
|
111
112
|
}
|
|
112
113
|
},
|
|
113
114
|
});
|
|
115
|
+
const discovery = options.discovery ?? true;
|
|
114
116
|
const adapter = new MimoAdapter({
|
|
117
|
+
discovery,
|
|
115
118
|
streamIdleTimeoutMs: options.streamIdleTimeoutMs,
|
|
116
119
|
tokens,
|
|
117
120
|
...options.onWarn === undefined ? {} : { onWarn: options.onWarn },
|
|
118
121
|
...options.resolveAttachments === undefined ? {} : { resolveAttachments: options.resolveAttachments },
|
|
119
122
|
hiddenModels: () => options.preferences.hiddenIds(MIMO_PROVIDER_ID),
|
|
120
123
|
userModels: () => options.preferences.extraIds(MIMO_PROVIDER_ID),
|
|
124
|
+
...options.cachedListing === undefined ? {} : { cachedListing: options.cachedListing },
|
|
125
|
+
...options.saveListing === undefined ? {} : { saveListing: options.saveListing },
|
|
121
126
|
fetchFn,
|
|
122
127
|
});
|
|
128
|
+
// Read the cached table now rather than at the first picker query: the Settings page
|
|
129
|
+
// reads the catalog synchronously, so a request-free start must already have it.
|
|
130
|
+
void adapter.hydrate().catch(() => undefined);
|
|
123
131
|
const spec = {
|
|
124
132
|
id: MIMO_PROVIDER_ID,
|
|
125
133
|
displayName: MIMO_PROVIDER_NAME,
|
|
126
134
|
codeUrl: MIMO_CODE_URL,
|
|
127
135
|
credentialFile: options.credentialFile,
|
|
128
|
-
// MiMo
|
|
129
|
-
|
|
136
|
+
// MiMo's own listing is best-effort: it may answer the public API's ids rather than
|
|
137
|
+
// the app roster's, and it may refuse the session credential. What the picker shows
|
|
138
|
+
// until (and unless) it answers is the built-in roster, so a failed read can never
|
|
139
|
+
// empty the picker.
|
|
140
|
+
discovery,
|
|
130
141
|
defaultModel: options.defaultModel ?? MIMO_DEFAULT_MODEL,
|
|
131
142
|
catalog: () => adapter.catalogEntries(),
|
|
132
143
|
catalogOwn: () => adapter.ownCatalogEntries(),
|
|
133
144
|
fallbackContextWindow: MIMO_CONTEXT_WINDOW,
|
|
134
|
-
discover: () =>
|
|
145
|
+
discover: (accessToken) => adapter.fetchCatalog(accessToken),
|
|
135
146
|
usage: async (_accessToken, account) => {
|
|
136
147
|
const held = await store.get(account);
|
|
137
148
|
if (held === undefined)
|
|
@@ -152,7 +163,7 @@ export function createMimoProvider(options) {
|
|
|
152
163
|
{ key: 'provider', value: MIMO_PROVIDER_NAME },
|
|
153
164
|
{ key: 'credentialFile', value: options.credentialFile },
|
|
154
165
|
],
|
|
155
|
-
applyDiscovery: () => { },
|
|
166
|
+
applyDiscovery: (ids) => { adapter.discover(ids); },
|
|
156
167
|
};
|
|
157
168
|
const controller = new ProviderController({
|
|
158
169
|
spec,
|