dsh-plugin-subscription-oauth 0.16.2 → 0.17.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/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/auth/workbuddy-flow.d.ts +1 -1
- package/lib/auth/workbuddy-flow.js +14 -9
- package/lib/client/index.js +10 -1
- package/lib/client.js +6 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +21 -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 +6 -0
- package/lib/providers/ark.d.ts +96 -10
- package/lib/providers/ark.js +176 -48
- package/lib/providers/catalog.d.ts +91 -0
- package/lib/providers/catalog.js +190 -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 +6 -0
- package/lib/providers/glm.d.ts +92 -16
- package/lib/providers/glm.js +172 -85
- package/lib/providers/grok.d.ts +106 -27
- package/lib/providers/grok.js +312 -128
- package/lib/providers/kimi-provider.d.ts +10 -0
- package/lib/providers/kimi-provider.js +8 -3
- package/lib/providers/kimi.d.ts +104 -32
- package/lib/providers/kimi.js +198 -129
- 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 +520 -55
- 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,16 @@ const EFFORT_LABELS = {
|
|
|
72
72
|
[FAST_RUNG]: { name: 'Highspeed', description: 'Kimi highspeed variant of this model' },
|
|
73
73
|
};
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Spellings the endpoint has retired, and the model each one became.
|
|
76
76
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
77
|
+
* A repair table, not a roster: nothing here decides which models exist. Each entry
|
|
78
|
+
* is consulted only for an id the live listing does not itself serve, so a spelling
|
|
79
|
+
* the endpoint reintroduces is dispatched exactly as the endpoint names it. The
|
|
80
|
+
* endpoint's own 401 message (`Please set model id as \`k3\``) is the general case
|
|
81
|
+
* and needs no table at all; this only saves the first failed round trip for a
|
|
82
|
+
* spelling already known to be gone.
|
|
80
83
|
*/
|
|
81
|
-
|
|
82
|
-
{
|
|
83
|
-
id: 'kimi-for-coding',
|
|
84
|
-
name: 'Kimi for Coding',
|
|
85
|
-
description: 'Stable subscription alias — 1M-token window',
|
|
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',
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: 'k3',
|
|
94
|
-
name: 'Kimi K3',
|
|
95
|
-
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
|
-
},
|
|
111
|
-
{
|
|
112
|
-
id: 'k3-256k',
|
|
113
|
-
name: 'Kimi K3 (256K)',
|
|
114
|
-
description: 'Flagship reasoning model pinned to the standard window',
|
|
115
|
-
contextWindow: KIMI_CONTEXT_WINDOW,
|
|
116
|
-
inputModalities: ['text', 'image'],
|
|
117
|
-
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
118
|
-
defaultEffort: 'max',
|
|
119
|
-
},
|
|
120
|
-
];
|
|
84
|
+
const RETIRED_MODEL_IDS = { 'k3[1m]': 'k3' };
|
|
121
85
|
/** One `reasoning.efforts` entry for a resolved model. */
|
|
122
86
|
function effortInfo(effort) {
|
|
123
87
|
const label = EFFORT_LABELS[effort] ?? { name: effort, description: '' };
|
|
@@ -145,42 +109,103 @@ function reasoningInfo(entry, fastWireId) {
|
|
|
145
109
|
/** Kimi Code adapter serving the `kimi` provider route. */
|
|
146
110
|
export class KimiAdapter extends LlmAdapter {
|
|
147
111
|
options;
|
|
148
|
-
/**
|
|
149
|
-
|
|
150
|
-
|
|
112
|
+
/**
|
|
113
|
+
* The catalog this route serves: entries derived from the endpoint's own listing,
|
|
114
|
+
* either the one just read or the one the cache kept from the last read.
|
|
115
|
+
*
|
|
116
|
+
* Undefined means neither source has produced a listing yet, so only the configured
|
|
117
|
+
* default model is offered until one does.
|
|
118
|
+
*/
|
|
119
|
+
live;
|
|
120
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
121
|
+
hydrated = false;
|
|
122
|
+
/** Image-generation model id, when the listing reported one. */
|
|
151
123
|
imageModel;
|
|
152
124
|
/**
|
|
153
125
|
* Fast-variant wire id per picker row, for the composer's quick-mode button.
|
|
154
126
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* the
|
|
127
|
+
* Derived from the listing: `kimi-for-coding-highspeed` is a row of its own there,
|
|
128
|
+
* and pairs with `kimi-for-coding` because its name is that of a listed model plus
|
|
129
|
+
* the provider's own fast word.
|
|
158
130
|
*/
|
|
159
131
|
fastVariants = new FastVariantIndex();
|
|
160
132
|
/** Ids the user added by hand, cached so the sync catalog can name them. */
|
|
161
133
|
userAddedIds = [];
|
|
134
|
+
/**
|
|
135
|
+
* Model ids the endpoint renamed during this session, and what each became.
|
|
136
|
+
*
|
|
137
|
+
* Learned from the endpoint's own refusal (`Please set model id as \`k3\``), so a
|
|
138
|
+
* spelling retired after this build shipped is repaired without a plugin update. The
|
|
139
|
+
* first turn pays one refused request; every later turn goes straight to the current id.
|
|
140
|
+
*/
|
|
141
|
+
renamed = new Map();
|
|
162
142
|
/** Tools already reported as flattened, so each is stated once. */
|
|
163
143
|
flattenedTools = new Set();
|
|
164
144
|
constructor(options) {
|
|
165
145
|
super();
|
|
166
146
|
this.options = options;
|
|
167
|
-
this.seedCuratedFastVariants();
|
|
168
147
|
}
|
|
169
148
|
/**
|
|
170
|
-
*
|
|
149
|
+
* Adopt the rows of one live listing as this route's catalog.
|
|
171
150
|
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
151
|
+
* Everything the picker shows is derived here from the endpoint's own answer: the
|
|
152
|
+
* rows that become picker entries, the fast variant that pairs with each row, and
|
|
153
|
+
* the image model that is called by an agent rather than chosen. Nothing about
|
|
154
|
+
* which models exist, or what each one is, is decided by this build.
|
|
155
|
+
*
|
|
156
|
+
* Fast variants ride with their base row instead of becoming rows of their own: they
|
|
157
|
+
* share the row's visibility switch and are reached by the composer's quick-mode
|
|
158
|
+
* button. A variant is recognized by its own name — the provider's fast word appended
|
|
159
|
+
* to a listed model's id — so no curated alias map is needed to pair them.
|
|
160
|
+
*
|
|
161
|
+
* @param rows - decoded rows of the endpoint's listing, in the endpoint's order.
|
|
162
|
+
* @returns the entries to publish, in picker order.
|
|
176
163
|
*/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
164
|
+
adoptListing(rows) {
|
|
165
|
+
const facts = rows
|
|
166
|
+
.map((row) => liveModelFacts(row))
|
|
167
|
+
.filter((fact) => fact !== undefined);
|
|
168
|
+
// Image models are never picker rows: they are agent-called, always on.
|
|
169
|
+
this.imageModel = facts.find((fact) => isImageModel(fact.id))?.id;
|
|
170
|
+
this.fastVariants.clear();
|
|
171
|
+
for (const fact of facts)
|
|
172
|
+
this.fastVariants.noteListed(fact.id);
|
|
173
|
+
for (const fact of facts) {
|
|
174
|
+
const base = this.fastVariants.pair(fact.id);
|
|
175
|
+
this.fastVariants.note(fact.id, () => base ?? baseModelOf(fact.id));
|
|
176
|
+
}
|
|
177
|
+
// Fold a variant onto its row and drop the ids that are not chat models, keeping the
|
|
178
|
+
// endpoint's order. A row's own facts describe the row; a variant's describe the
|
|
179
|
+
// variant, so a row that is also listed is never overwritten by its variant.
|
|
180
|
+
const byId = new Map();
|
|
181
|
+
for (const fact of facts) {
|
|
182
|
+
if (NON_CHAT_ID.test(fact.id))
|
|
183
|
+
continue;
|
|
184
|
+
const id = isFastVariant(fact.id) ? this.fastVariants.pair(fact.id) ?? baseModelOf(fact.id) : fact.id;
|
|
185
|
+
if (id === fact.id || !byId.has(id))
|
|
186
|
+
byId.set(id, { ...fact, id });
|
|
187
|
+
}
|
|
188
|
+
return [...byId.values()].map((fact) => liveEntry(fact, [], KIMI_CONTEXT_WINDOW, 'Reported by the Kimi Code model list'));
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
192
|
+
*
|
|
193
|
+
* Called before the first catalog query. A read failure is a cache miss, never a
|
|
194
|
+
* failure: the route simply has no listing until one is fetched.
|
|
195
|
+
*/
|
|
196
|
+
async hydrate() {
|
|
197
|
+
if (this.hydrated)
|
|
198
|
+
return;
|
|
199
|
+
this.hydrated = true;
|
|
200
|
+
if (this.live !== undefined || this.options.cachedListing === undefined)
|
|
201
|
+
return;
|
|
202
|
+
try {
|
|
203
|
+
const rows = await this.options.cachedListing();
|
|
204
|
+
if (Array.isArray(rows) && rows.length > 0)
|
|
205
|
+
this.live = this.adoptListing(rows);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
this.options.onWarn?.(`cached model catalog unavailable (${errorChain(error)})`);
|
|
184
209
|
}
|
|
185
210
|
}
|
|
186
211
|
/** The fast pair one picker id belongs to, from either side. */
|
|
@@ -219,6 +244,7 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
219
244
|
// must not appear in the picker, and Auto must not try it.
|
|
220
245
|
if (await this.options.tokens.session() === undefined)
|
|
221
246
|
return [];
|
|
247
|
+
await this.hydrate();
|
|
222
248
|
await this.syncUserModels();
|
|
223
249
|
const hidden = await this.hidden();
|
|
224
250
|
// One row per model, always under the model's own id: the host validates every GUI
|
|
@@ -226,8 +252,7 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
226
252
|
// picker with no way back to the standard model. Fast mode is the row's `fast`
|
|
227
253
|
// reasoning rung instead (see `stream`).
|
|
228
254
|
const imageless = this.catalog().filter((entry) => !isImageModel(entry.id));
|
|
229
|
-
return resolveCatalog(imageless, [], hidden).entries.map((entry) => ({
|
|
230
|
-
provider: KIMI_PROVIDER_ID,
|
|
255
|
+
return resolveCatalog(imageless, [], hidden).entries.map((entry) => ({ provider: KIMI_PROVIDER_ID,
|
|
231
256
|
id: entry.id,
|
|
232
257
|
name: entry.name,
|
|
233
258
|
...entry.description === undefined ? {} : { description: entry.description },
|
|
@@ -256,8 +281,12 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
256
281
|
}
|
|
257
282
|
}
|
|
258
283
|
async resolveModel(_provider, model) {
|
|
284
|
+
await this.hydrate();
|
|
259
285
|
await this.syncUserModels();
|
|
260
|
-
|
|
286
|
+
// A retired spelling answers with the model that replaced it, so a session that
|
|
287
|
+
// saved the old id shows the right name, window and ladder before it dispatches.
|
|
288
|
+
const repaired = this.repairModelId(model);
|
|
289
|
+
const entry = this.entryFor(repaired ?? model);
|
|
261
290
|
if (entry === undefined) {
|
|
262
291
|
// An unknown id is still dispatchable: the endpoint accepts the id it
|
|
263
292
|
// reports, and a model added after this build should not be refused. It
|
|
@@ -277,8 +306,9 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
277
306
|
// picker shows the row under its own name and the mode as the effort.
|
|
278
307
|
const fastWireId = this.fastVariants.variantOf(entry.id);
|
|
279
308
|
const reasoning = reasoningInfo(entry, fastWireId);
|
|
280
|
-
//
|
|
281
|
-
//
|
|
309
|
+
// The listing's own id is what resolves here — a renamed or retired spelling is
|
|
310
|
+
// reported as the model the endpoint now calls it — which is what keeps a selection
|
|
311
|
+
// dispatchable and the picker able to show it.
|
|
282
312
|
return {
|
|
283
313
|
provider: KIMI_PROVIDER_ID,
|
|
284
314
|
id: entry.id,
|
|
@@ -294,10 +324,18 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
294
324
|
// Fast mode rides the reasoning channel: the rung is the variant's own fast word, not
|
|
295
325
|
// a model id of its own, so the row the picker selected stays dispatchable and the user
|
|
296
326
|
// can always select the standard model again. Only the wire id changes.
|
|
297
|
-
const fastWireId = this.fastVariants.variantOf(options.model);
|
|
298
327
|
const effort = String(options.reasoningEffort ?? '');
|
|
328
|
+
// A request that already names a fast variant keeps that wire id: it is a model id of
|
|
329
|
+
// its own rather than a spelling of the row. Every other id is sent exactly as the
|
|
330
|
+
// endpoint's listing names it, except a spelling the endpoint has retired, which goes
|
|
331
|
+
// out as the model that replaced it — a session that saved `k3[1m]` repairs itself on
|
|
332
|
+
// the next turn instead of failing with HTTP 401.
|
|
333
|
+
const selected = isFastVariant(options.model)
|
|
334
|
+
? options.model
|
|
335
|
+
: this.repairModelId(options.model) ?? options.model;
|
|
336
|
+
const fastWireId = this.fastVariants.variantOf(selected);
|
|
299
337
|
const fast = fastWireId !== undefined && effort === fastWordOf(fastWireId);
|
|
300
|
-
const model = fast ? fastWireId :
|
|
338
|
+
const model = fast ? fastWireId : selected;
|
|
301
339
|
const call = { ...options, model };
|
|
302
340
|
// Kimi has no fast-variant effort; the variant already carries the behaviour.
|
|
303
341
|
if (fast)
|
|
@@ -314,6 +352,12 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
314
352
|
onAccountSwitch: (from, to, reason) => {
|
|
315
353
|
this.options.onWarn?.(`kimi: account ${from} allowance spent (${reason}); switching to ${to}`);
|
|
316
354
|
},
|
|
355
|
+
// The endpoint names the model id to use when a request asks for one it has
|
|
356
|
+
// retired; remembering it repairs this session rather than failing the turn.
|
|
357
|
+
onModelRenamed: (from, to) => {
|
|
358
|
+
this.renamed.set(from, to);
|
|
359
|
+
this.options.onWarn?.(`kimi: model ${from} is retired; using ${to}`);
|
|
360
|
+
},
|
|
317
361
|
...this.options.onCredentialRejected === undefined
|
|
318
362
|
? {}
|
|
319
363
|
: { onCredentialRejected: this.options.onCredentialRejected },
|
|
@@ -339,36 +383,48 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
339
383
|
return [...this.shippedCatalog(), ...this.addedEntries()];
|
|
340
384
|
}
|
|
341
385
|
/**
|
|
342
|
-
* The catalog to present: the
|
|
343
|
-
*
|
|
344
|
-
* build does not know.
|
|
386
|
+
* The catalog to present: the models the endpoint's listing named, in its own order,
|
|
387
|
+
* each with the metadata that listing stated, then the configured extras.
|
|
345
388
|
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
389
|
+
* There is no shipped roster to fall back to. A listing is read once and cached (see
|
|
390
|
+
* {@link hydrate} and {@link fetchCatalog}), and that cached table is what the picker
|
|
391
|
+
* shows until the next read, so a model released, renamed or retired server-side is
|
|
392
|
+
* reflected without a plugin update or a request per refresh.
|
|
349
393
|
*/
|
|
350
394
|
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;
|
|
395
|
+
return composeCatalog({
|
|
396
|
+
curated: [],
|
|
397
|
+
...this.live === undefined ? {} : { live: this.live },
|
|
398
|
+
...this.options.extraModels === undefined ? {} : { extra: this.options.extraModels },
|
|
399
|
+
defaultModel: this.options.defaultModel ?? KIMI_DEFAULT_MODEL,
|
|
400
|
+
fallbackContextWindow: KIMI_CONTEXT_WINDOW,
|
|
401
|
+
extraDescription: 'Extra model from the plugin configuration',
|
|
402
|
+
});
|
|
368
403
|
}
|
|
369
|
-
/** Resolve a requested model id onto
|
|
404
|
+
/** Resolve a requested model id onto the entry the endpoint's listing described. */
|
|
370
405
|
entryFor(model) {
|
|
371
|
-
return findEntry(
|
|
406
|
+
return findEntry(this.live ?? [], model);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The model one wire id names, when the endpoint has retired the spelling.
|
|
410
|
+
*
|
|
411
|
+
* Consulted only for an id the current listing does not serve, so an id the endpoint
|
|
412
|
+
* itself reports is always dispatched exactly as reported.
|
|
413
|
+
*
|
|
414
|
+
* @param model - the requested model id.
|
|
415
|
+
* @returns the current spelling, or undefined when the id is not a known retirement.
|
|
416
|
+
*/
|
|
417
|
+
repairModelId(model) {
|
|
418
|
+
if (this.entryFor(model) !== undefined)
|
|
419
|
+
return undefined;
|
|
420
|
+
// A fast variant is a wire id rather than a row: a session that saved one belongs on
|
|
421
|
+
// the row that owns it, where the quick-mode rung is what switches the variant on.
|
|
422
|
+
const pair = this.fastVariants.pairOf(model);
|
|
423
|
+
if (pair !== undefined)
|
|
424
|
+
return pair.baseId;
|
|
425
|
+
// Prefer what this session was actually told over the table this build shipped: the
|
|
426
|
+
// endpoint's own answer is newer than any release.
|
|
427
|
+
return this.renamed.get(model) ?? RETIRED_MODEL_IDS[model];
|
|
372
428
|
}
|
|
373
429
|
/** Re-read the ids the user added by hand. */
|
|
374
430
|
async syncUserModels() {
|
|
@@ -396,11 +452,22 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
396
452
|
.filter((id) => !known.has(id))
|
|
397
453
|
.map((id) => fallbackEntry(id, 'Added by hand in Settings', KIMI_CONTEXT_WINDOW));
|
|
398
454
|
}
|
|
399
|
-
/**
|
|
455
|
+
/**
|
|
456
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
457
|
+
*
|
|
458
|
+
* {@link fetchCatalog} already derived the entries from the rows it read, and the
|
|
459
|
+
* controller reports those same ids back here; the entries it recorded are kept. A
|
|
460
|
+
* caller that only has ids (a test, another discovery path) gets conservative entries
|
|
461
|
+
* for them.
|
|
462
|
+
*
|
|
463
|
+
* @param ids - model ids the caller reports.
|
|
464
|
+
*/
|
|
400
465
|
discover(ids) {
|
|
401
|
-
this.
|
|
466
|
+
const known = new Map((this.live ?? []).map((entry) => [entry.id, entry]));
|
|
467
|
+
this.live = ids.map((id) => known.get(id)
|
|
468
|
+
?? liveEntry({ id }, [], KIMI_CONTEXT_WINDOW, 'Reported by the Kimi Code model list'));
|
|
402
469
|
}
|
|
403
|
-
/** The
|
|
470
|
+
/** The catalog, for the Settings page's model list. */
|
|
404
471
|
catalogEntries() {
|
|
405
472
|
return this.catalog();
|
|
406
473
|
}
|
|
@@ -476,7 +543,20 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
476
543
|
}
|
|
477
544
|
};
|
|
478
545
|
}
|
|
479
|
-
/**
|
|
546
|
+
/**
|
|
547
|
+
* Read the endpoint's model listing and adopt it as this route's catalog.
|
|
548
|
+
*
|
|
549
|
+
* This is the one place a model table is produced, and it is the endpoint's own
|
|
550
|
+
* answer: what the listing names is what the picker offers, with the display name,
|
|
551
|
+
* context window, modalities and reasoning ladder the listing states. The rows are
|
|
552
|
+
* also handed to {@link KimiAdapterOptions.saveListing}, so the next session starts
|
|
553
|
+
* from this table instead of asking again — the Settings "refresh models" action is
|
|
554
|
+
* what re-reads it.
|
|
555
|
+
*
|
|
556
|
+
* @param accessToken - bearer for the account.
|
|
557
|
+
* @param signal - optional cancellation.
|
|
558
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
559
|
+
*/
|
|
480
560
|
async fetchCatalog(accessToken, signal) {
|
|
481
561
|
if (!this.options.discovery)
|
|
482
562
|
return undefined;
|
|
@@ -488,45 +568,34 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
488
568
|
});
|
|
489
569
|
if (!response.ok) {
|
|
490
570
|
if (!isMissingOrInvalidCredential(await httpLlmError(response, 'Kimi models'))) {
|
|
491
|
-
this.options.onWarn?.(`Kimi model discovery failed (HTTP ${response.status});
|
|
571
|
+
this.options.onWarn?.(`Kimi model discovery failed (HTTP ${response.status}); keeping the last catalog`);
|
|
492
572
|
}
|
|
493
573
|
return undefined;
|
|
494
574
|
}
|
|
495
575
|
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');
|
|
576
|
+
const rows = Array.isArray(payload.data) ? payload.data : [];
|
|
577
|
+
const entries = this.adoptListing(rows);
|
|
578
|
+
if (entries.length === 0) {
|
|
579
|
+
this.options.onWarn?.('Kimi model discovery returned an empty catalog; keeping the last catalog');
|
|
522
580
|
return undefined;
|
|
523
581
|
}
|
|
524
|
-
|
|
582
|
+
this.live = entries;
|
|
583
|
+
// The cached table is the endpoint's answer, kept so the next session does not ask
|
|
584
|
+
// again. A cache failure never fails the read that produced it.
|
|
585
|
+
if (this.options.saveListing !== undefined) {
|
|
586
|
+
try {
|
|
587
|
+
await this.options.saveListing(rows);
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
this.options.onWarn?.(`model catalog cache write failed (${errorChain(error)})`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return entries.map((entry) => entry.id);
|
|
525
594
|
}
|
|
526
595
|
catch (error) {
|
|
527
596
|
if (signal?.aborted === true)
|
|
528
597
|
return undefined;
|
|
529
|
-
this.options.onWarn?.(`Kimi model discovery failed;
|
|
598
|
+
this.options.onWarn?.(`Kimi model discovery failed; keeping the last catalog (${errorChain(error)})`);
|
|
530
599
|
return undefined;
|
|
531
600
|
}
|
|
532
601
|
}
|
|
@@ -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,
|