dsh-plugin-subscription-oauth 0.16.3 → 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/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 +501 -59
- package/package.json +1 -1
package/lib/providers/grok.js
CHANGED
|
@@ -16,7 +16,7 @@ import { TokenStore, finiteInstant, nonEmpty } from '../auth/store.js';
|
|
|
16
16
|
import { TokenManager } from '../auth/tokens.js';
|
|
17
17
|
import { ProviderController } from '../auth/provider-controller.js';
|
|
18
18
|
import { proxiedFetch } from '../http.js';
|
|
19
|
-
import { fallbackEntry, findEntry, resolveCatalog } from './catalog.js';
|
|
19
|
+
import { composeCatalog, fallbackEntry, findEntry, liveEntry, liveModelFacts, resolveCatalog } from './catalog.js';
|
|
20
20
|
import { isMissingOrInvalidCredential } from './common.js';
|
|
21
21
|
import { streamProviderResponses, accountPoolHooks } from './responses-stream.js';
|
|
22
22
|
import { readGrokUsage } from './usage.js';
|
|
@@ -66,11 +66,14 @@ const EFFORT_LABELS = {
|
|
|
66
66
|
[FAST_RUNG]: { name: 'Fast', description: 'xAI fast variant of this model' },
|
|
67
67
|
};
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* The cold-start seed: what this build knows about xAI's models before either of the
|
|
70
|
+
* account's own listings has been read.
|
|
70
71
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
72
|
+
* A seed, never a roster. It decides which ids exist only until a listing has been read
|
|
73
|
+
* — the cached one at startup, or a fresh one on "refresh models" — and afterwards it
|
|
74
|
+
* describes only the fields those listings left silent. A fact the endpoint states,
|
|
75
|
+
* a context window included, always wins over anything here, so a model released,
|
|
76
|
+
* renamed or retired server-side reaches the picker without a plugin release.
|
|
74
77
|
*/
|
|
75
78
|
export const GROK_MODELS = [
|
|
76
79
|
{
|
|
@@ -276,6 +279,75 @@ export async function refreshGrokToken(input) {
|
|
|
276
279
|
}
|
|
277
280
|
return parseGrokTokenPayload(payload, input.refreshToken);
|
|
278
281
|
}
|
|
282
|
+
/** Description the picker shows for a model only the endpoint's listing describes. */
|
|
283
|
+
const GROK_LIVE_DESCRIPTION = 'Reported by the xAI model list';
|
|
284
|
+
/**
|
|
285
|
+
* Split one cached or fresh payload into the two listings it holds.
|
|
286
|
+
*
|
|
287
|
+
* The payload is this route's own object, so its shape is validated rather than trusted:
|
|
288
|
+
* a document written by an older build, or corrupted on disk, is a cache miss and never
|
|
289
|
+
* a crash on the way to the picker.
|
|
290
|
+
*
|
|
291
|
+
* @param payload - the cached or freshly assembled `{ public, cli }` payload.
|
|
292
|
+
* @returns the two row lists, or undefined when the payload is not this route's.
|
|
293
|
+
*/
|
|
294
|
+
function listingOf(payload) {
|
|
295
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload))
|
|
296
|
+
return undefined;
|
|
297
|
+
const record = payload;
|
|
298
|
+
const publicRows = record['public'];
|
|
299
|
+
if (!Array.isArray(publicRows))
|
|
300
|
+
return undefined;
|
|
301
|
+
const cliRows = record['cli'];
|
|
302
|
+
return { public: publicRows, cli: Array.isArray(cliRows) ? cliRows : [] };
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Read the capability metadata and the ids one CLI catalog answer named.
|
|
306
|
+
*
|
|
307
|
+
* The shape is xAI's own rather than the OpenAI-compatible one the shared row reader
|
|
308
|
+
* handles: a context window under `context_window`, and a ladder under
|
|
309
|
+
* `reasoning_efforts[].value` gated by `supports_reasoning_effort`. A row that states
|
|
310
|
+
* nothing usable contributes only its id, because the id list is what folds a fast
|
|
311
|
+
* variant onto its base row.
|
|
312
|
+
*
|
|
313
|
+
* @param rows - decoded rows of the CLI catalog answer.
|
|
314
|
+
* @returns the metadata by model id, and every id the catalog named.
|
|
315
|
+
*/
|
|
316
|
+
function cliListingOf(rows) {
|
|
317
|
+
const metadata = new Map();
|
|
318
|
+
const ids = [];
|
|
319
|
+
for (const row of rows) {
|
|
320
|
+
if (typeof row !== 'object' || row === null || Array.isArray(row))
|
|
321
|
+
continue;
|
|
322
|
+
const entry = row;
|
|
323
|
+
const rawId = entry['id'];
|
|
324
|
+
const id = typeof rawId === 'string' && rawId.length > 0 ? rawId : undefined;
|
|
325
|
+
if (id === undefined)
|
|
326
|
+
continue;
|
|
327
|
+
ids.push(id);
|
|
328
|
+
const rawContext = entry['context_window'];
|
|
329
|
+
const contextWindow = typeof rawContext === 'number' && Number.isFinite(rawContext) ? rawContext : undefined;
|
|
330
|
+
const supportsEffort = entry['supports_reasoning_effort'] === true;
|
|
331
|
+
const rawEfforts = entry['reasoning_efforts'];
|
|
332
|
+
const efforts = supportsEffort && Array.isArray(rawEfforts)
|
|
333
|
+
? rawEfforts
|
|
334
|
+
.map((effort) => (typeof effort === 'object' && effort !== null
|
|
335
|
+
? effort['value']
|
|
336
|
+
: undefined))
|
|
337
|
+
.filter((value) => typeof value === 'string' && value.length > 0)
|
|
338
|
+
: [];
|
|
339
|
+
const requested = entry['reasoning_effort'];
|
|
340
|
+
const defaultEffort = supportsEffort && typeof requested === 'string' && efforts.includes(requested)
|
|
341
|
+
? requested
|
|
342
|
+
: undefined;
|
|
343
|
+
metadata.set(id, {
|
|
344
|
+
...contextWindow === undefined ? {} : { contextWindow },
|
|
345
|
+
...efforts.length === 0 ? {} : { reasoningEfforts: efforts },
|
|
346
|
+
...defaultEffort === undefined ? {} : { defaultEffort },
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return { metadata, ids };
|
|
350
|
+
}
|
|
279
351
|
/** Model ids that are clearly not chat models. */
|
|
280
352
|
const NON_CHAT_ID = /(?:^|[-_/])(?:image|video|imagine|embed|embedding|whisper|tts|vision-only)/i;
|
|
281
353
|
/** Model ids that are image-generation models, matched per provider's naming. */
|
|
@@ -438,13 +510,23 @@ export function stripContext(id) {
|
|
|
438
510
|
/** Grok wire adapter: one instance serves the `grok` provider route. */
|
|
439
511
|
export class GrokAdapter extends LlmAdapter {
|
|
440
512
|
options;
|
|
441
|
-
/**
|
|
442
|
-
|
|
513
|
+
/**
|
|
514
|
+
* The catalog this route serves: entries derived from the endpoint's own two
|
|
515
|
+
* listings, either the pair just read or the pair the cache kept from the last read.
|
|
516
|
+
*
|
|
517
|
+
* Undefined means neither source has produced a listing yet, so the cold-start seed
|
|
518
|
+
* is all this build can honestly offer.
|
|
519
|
+
*/
|
|
520
|
+
live;
|
|
521
|
+
/** Whether the cached listing has been read; it is read at most once per session. */
|
|
522
|
+
hydrated = false;
|
|
443
523
|
/** Capability metadata from the CLI catalog, keyed by model id. */
|
|
444
524
|
metadata = new Map();
|
|
445
525
|
/** Fast-variant wire id per base model, for the picker's quick-mode button. */
|
|
446
526
|
fastVariants = new FastVariantIndex();
|
|
447
|
-
/**
|
|
527
|
+
/** The context variant one base row dispatches to, so `@500k` is never typed by hand. */
|
|
528
|
+
defaultContext = new Map();
|
|
529
|
+
/** Image-generation model id, when the listing reported one. */
|
|
448
530
|
imageModel;
|
|
449
531
|
/** Ids the user added by hand, cached so the sync catalog can name them. */
|
|
450
532
|
userAddedIds = [];
|
|
@@ -452,6 +534,146 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
452
534
|
super();
|
|
453
535
|
this.options = options;
|
|
454
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Adopt the two listings of one live read as this route's catalog.
|
|
539
|
+
*
|
|
540
|
+
* Everything the picker shows is derived here from the endpoint's own answers: the
|
|
541
|
+
* public `/models` rows decide membership and state each model's display name, window
|
|
542
|
+
* and modalities, and the account's CLI catalog states the reasoning ladder and names
|
|
543
|
+
* the fast variants — the one thing the public list omits.
|
|
544
|
+
*
|
|
545
|
+
* A fast variant or an `@Nk` context variant rides with its base row instead of becoming
|
|
546
|
+
* a row of its own: the variant is a wire id the composer dispatches, never a picker
|
|
547
|
+
* row, so the user always has a way back to the standard model.
|
|
548
|
+
*
|
|
549
|
+
* @param payload - the cached or freshly assembled `{ public, cli }` payload.
|
|
550
|
+
* @returns the entries to publish, in the endpoint's own order; undefined when the
|
|
551
|
+
* payload names no model at all, so the caller keeps whatever it already had.
|
|
552
|
+
*/
|
|
553
|
+
adoptListing(payload) {
|
|
554
|
+
const listing = listingOf(payload);
|
|
555
|
+
if (listing === undefined)
|
|
556
|
+
return undefined;
|
|
557
|
+
const facts = listing.public
|
|
558
|
+
.map((row) => liveModelFacts(row))
|
|
559
|
+
.filter((fact) => fact !== undefined);
|
|
560
|
+
const cli = cliListingOf(listing.cli);
|
|
561
|
+
const chat = facts.filter((fact) => !NON_CHAT_ID.test(fact.id) && !isImageModel(fact.id));
|
|
562
|
+
// Membership comes from the public list, so a payload that named none of it is a miss
|
|
563
|
+
// rather than an empty picker.
|
|
564
|
+
if (chat.length === 0)
|
|
565
|
+
return undefined;
|
|
566
|
+
// One payload is the whole truth, so every table derived from an earlier read is
|
|
567
|
+
// replaced rather than merged: a model the endpoint has retired cannot survive in
|
|
568
|
+
// state left behind by a read that no longer names it.
|
|
569
|
+
this.metadata.clear();
|
|
570
|
+
this.fastVariants.clear();
|
|
571
|
+
this.defaultContext.clear();
|
|
572
|
+
for (const [id, meta] of cli.metadata)
|
|
573
|
+
this.metadata.set(id, meta);
|
|
574
|
+
// Image models are never picker rows: an agent calls them, always on.
|
|
575
|
+
this.imageModel = [...facts.map((fact) => fact.id), ...cli.ids].find((id) => isImageModel(id));
|
|
576
|
+
// Every id either listing named is what a variant may pair with; the pairing itself is
|
|
577
|
+
// the longest listed prefix of the variant's own name.
|
|
578
|
+
const wireIds = [
|
|
579
|
+
...chat.map((fact) => fact.id),
|
|
580
|
+
...cli.ids.filter((id) => !NON_CHAT_ID.test(id) && !isImageModel(id)),
|
|
581
|
+
];
|
|
582
|
+
for (const id of wireIds)
|
|
583
|
+
this.fastVariants.noteListed(id);
|
|
584
|
+
this.noteWireVariants(chat.map((fact) => fact.id));
|
|
585
|
+
this.noteWireVariants(cli.ids);
|
|
586
|
+
// Fold each variant onto its row, keeping the endpoint's order. A row's own facts are
|
|
587
|
+
// its identity and win outright; a variant only fills what they omit, except a context
|
|
588
|
+
// variant, whose window is the one the row actually dispatches to.
|
|
589
|
+
const own = new Map();
|
|
590
|
+
const folds = new Map();
|
|
591
|
+
const windows = new Map();
|
|
592
|
+
const order = [];
|
|
593
|
+
for (const fact of chat) {
|
|
594
|
+
const contextVariant = contextOf(fact.id) !== undefined;
|
|
595
|
+
const rowId = isFastVariant(fact.id)
|
|
596
|
+
? this.fastVariants.pair(fact.id) ?? baseModelOf(fact.id)
|
|
597
|
+
: contextVariant ? stripContext(fact.id) : fact.id;
|
|
598
|
+
if (rowId === '')
|
|
599
|
+
continue;
|
|
600
|
+
if (!own.has(rowId) && !folds.has(rowId))
|
|
601
|
+
order.push(rowId);
|
|
602
|
+
if (contextVariant)
|
|
603
|
+
windows.set(rowId, Math.max(windows.get(rowId) ?? 0, fact.contextWindow ?? 0));
|
|
604
|
+
if (rowId === fact.id) {
|
|
605
|
+
if (!own.has(rowId))
|
|
606
|
+
own.set(rowId, fact);
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
folds.set(rowId, [...folds.get(rowId) ?? [], fact]);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return order.map((rowId) => {
|
|
613
|
+
const stated = own.get(rowId);
|
|
614
|
+
const variants = folds.get(rowId) ?? [];
|
|
615
|
+
const fill = (pick) => {
|
|
616
|
+
const ownValue = stated === undefined ? undefined : pick(stated);
|
|
617
|
+
if (ownValue !== undefined)
|
|
618
|
+
return ownValue;
|
|
619
|
+
for (const variant of variants) {
|
|
620
|
+
const value = pick(variant);
|
|
621
|
+
if (value !== undefined)
|
|
622
|
+
return value;
|
|
623
|
+
}
|
|
624
|
+
return undefined;
|
|
625
|
+
};
|
|
626
|
+
const widest = windows.get(rowId);
|
|
627
|
+
const statedWindow = stated?.contextWindow;
|
|
628
|
+
const contextWindow = widest === undefined || (statedWindow ?? 0) >= widest ? statedWindow : widest;
|
|
629
|
+
const inputModalities = fill((fact) => fact.inputModalities);
|
|
630
|
+
const reasoningEfforts = fill((fact) => fact.reasoningEfforts);
|
|
631
|
+
const defaultEffort = fill((fact) => fact.defaultEffort);
|
|
632
|
+
// The CLI catalog is a listing too, so its figures only fill what the public row
|
|
633
|
+
// left silent: a window or a ladder the endpoint already stated is never overridden,
|
|
634
|
+
// and the shipped seed never overrides either of them.
|
|
635
|
+
const meta = [rowId, ...variants.map((variant) => variant.id)]
|
|
636
|
+
.map((id) => this.metadata.get(id))
|
|
637
|
+
.find((entry) => entry !== undefined);
|
|
638
|
+
const context = contextWindow ?? meta?.contextWindow;
|
|
639
|
+
const efforts = reasoningEfforts ?? meta?.reasoningEfforts;
|
|
640
|
+
const metaDefault = meta?.defaultEffort;
|
|
641
|
+
// A default the ladder does not offer would leave the picker with a rung it cannot
|
|
642
|
+
// select, so it is stated only when the ladder that won actually contains it.
|
|
643
|
+
const fallback = defaultEffort
|
|
644
|
+
?? (metaDefault === undefined || efforts !== undefined && !efforts.includes(metaDefault) ? undefined : metaDefault);
|
|
645
|
+
const described = {
|
|
646
|
+
...stated ?? { id: rowId },
|
|
647
|
+
...context === undefined ? {} : { contextWindow: context },
|
|
648
|
+
...efforts === undefined ? {} : { reasoningEfforts: efforts },
|
|
649
|
+
...fallback === undefined ? {} : { defaultEffort: fallback },
|
|
650
|
+
...inputModalities === undefined ? {} : { inputModalities },
|
|
651
|
+
};
|
|
652
|
+
return liveEntry(described, GROK_MODELS, GROK_CONTEXT_WINDOW, GROK_LIVE_DESCRIPTION);
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Read the cached listing, once, so the picker works without asking the endpoint.
|
|
657
|
+
*
|
|
658
|
+
* Called before the first catalog query, and fired at construction by the route's
|
|
659
|
+
* factory because the Settings page reads the catalog synchronously. A read failure is
|
|
660
|
+
* a cache miss, never a failure: the route simply has no listing until one is fetched.
|
|
661
|
+
*/
|
|
662
|
+
async hydrate() {
|
|
663
|
+
if (this.hydrated)
|
|
664
|
+
return;
|
|
665
|
+
this.hydrated = true;
|
|
666
|
+
if (this.live !== undefined || this.options.cachedListing === undefined)
|
|
667
|
+
return;
|
|
668
|
+
try {
|
|
669
|
+
const entries = this.adoptListing(await this.options.cachedListing());
|
|
670
|
+
if (entries !== undefined)
|
|
671
|
+
this.live = entries;
|
|
672
|
+
}
|
|
673
|
+
catch (error) {
|
|
674
|
+
this.options.onWarn?.(`cached model catalog unavailable (${errorChain(error)})`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
455
677
|
providerInfo(provider) {
|
|
456
678
|
return { id: provider, name: GROK_PROVIDER_NAME };
|
|
457
679
|
}
|
|
@@ -464,6 +686,7 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
464
686
|
// must not appear in the picker, and Auto must not try it.
|
|
465
687
|
if (await this.options.tokens.session() === undefined)
|
|
466
688
|
return [];
|
|
689
|
+
await this.hydrate();
|
|
467
690
|
await this.syncUserModels();
|
|
468
691
|
const hidden = await this.hidden();
|
|
469
692
|
// One row per model, always under the model's own id: the host validates every GUI
|
|
@@ -491,14 +714,18 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
491
714
|
}
|
|
492
715
|
}
|
|
493
716
|
async resolveModel(_provider, model) {
|
|
717
|
+
await this.hydrate();
|
|
494
718
|
await this.syncUserModels();
|
|
495
719
|
// A fast variant is not a catalog row of its own: it answers with its base model's
|
|
496
|
-
// metadata — the same
|
|
720
|
+
// metadata — the same window, name and ladder the picker shows — while the id
|
|
497
721
|
// stays the fast wire id the request must send.
|
|
498
722
|
const pair = this.fastPairOf(model);
|
|
499
723
|
const entry = findEntry(this.catalog(), model)
|
|
500
724
|
?? (pair === undefined ? undefined : findEntry(this.catalog(), pair.baseId));
|
|
501
725
|
if (entry === undefined) {
|
|
726
|
+
// An unknown id is still dispatchable: the endpoint accepts the id it reports, and a
|
|
727
|
+
// model added after this build should not be refused. It gets the conservative
|
|
728
|
+
// defaults, described by the cold-start seed rather than by a guess.
|
|
502
729
|
const template = findEntry(GROK_MODELS, 'grok-4.6');
|
|
503
730
|
const reasoning = template === undefined ? undefined : reasoningInfo(template);
|
|
504
731
|
return {
|
|
@@ -567,45 +794,25 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
567
794
|
return [...this.shippedCatalog(), ...this.addedEntries()];
|
|
568
795
|
}
|
|
569
796
|
/**
|
|
570
|
-
* The catalog to present:
|
|
571
|
-
*
|
|
572
|
-
*
|
|
797
|
+
* The catalog to present: the models both of the endpoint's listings named, in the
|
|
798
|
+
* public list's own order, each with the metadata the listings stated.
|
|
799
|
+
*
|
|
800
|
+
* There is no shipped roster to fall back to. The listings are read once and cached
|
|
801
|
+
* (see {@link hydrate} and {@link fetchCatalog}), and that cached table is what the
|
|
802
|
+
* picker shows until the next read, so a model released, renamed or retired
|
|
803
|
+
* server-side is reflected without a plugin update or a request per refresh.
|
|
804
|
+
*
|
|
805
|
+
* {@link GROK_MODELS} is only the cold-start seed: it decides membership while neither
|
|
806
|
+
* listing has been read, and afterwards describes the fields the listings left silent.
|
|
807
|
+
* A live fact, a context window included, always wins over it.
|
|
573
808
|
*/
|
|
574
809
|
shippedCatalog() {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
seen.add(id);
|
|
581
|
-
const meta = this.metadata.get(id);
|
|
582
|
-
extra.push({
|
|
583
|
-
id,
|
|
584
|
-
name: id,
|
|
585
|
-
description: 'Reported by the xAI model list',
|
|
586
|
-
contextWindow: meta?.contextWindow ?? GROK_CONTEXT_WINDOW,
|
|
587
|
-
inputModalities: ['text', 'image'],
|
|
588
|
-
// A ladder is advertised only when the provider's own catalog gave one.
|
|
589
|
-
...meta?.reasoningEfforts === undefined ? {} : { reasoningEfforts: meta.reasoningEfforts },
|
|
590
|
-
...meta?.defaultEffort === undefined ? {} : { defaultEffort: meta.defaultEffort },
|
|
591
|
-
});
|
|
592
|
-
}
|
|
593
|
-
// The seeds also pick up live metadata (reasoning ladder, default effort). The
|
|
594
|
-
// curated context window is never narrowed by a live figure: the CLI catalog states
|
|
595
|
-
// 256k for `grok-4.7`, which serves 500k, and a live read may only widen what this
|
|
596
|
-
// build ships.
|
|
597
|
-
const seeded = GROK_MODELS.map((entry) => {
|
|
598
|
-
const meta = this.metadata.get(entry.id);
|
|
599
|
-
if (meta === undefined)
|
|
600
|
-
return entry;
|
|
601
|
-
return {
|
|
602
|
-
...entry,
|
|
603
|
-
contextWindow: Math.max(entry.contextWindow, meta.contextWindow ?? 0),
|
|
604
|
-
...meta.reasoningEfforts === undefined ? {} : { reasoningEfforts: meta.reasoningEfforts },
|
|
605
|
-
...meta.defaultEffort === undefined ? {} : { defaultEffort: meta.defaultEffort },
|
|
606
|
-
};
|
|
810
|
+
return composeCatalog({
|
|
811
|
+
curated: GROK_MODELS,
|
|
812
|
+
...this.live === undefined ? {} : { live: this.live },
|
|
813
|
+
fallbackContextWindow: GROK_CONTEXT_WINDOW,
|
|
814
|
+
liveDescription: GROK_LIVE_DESCRIPTION,
|
|
607
815
|
});
|
|
608
|
-
return [...seeded, ...extra];
|
|
609
816
|
}
|
|
610
817
|
/** Re-read the ids the user added by hand. */
|
|
611
818
|
async syncUserModels() {
|
|
@@ -633,13 +840,21 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
633
840
|
.filter((id) => !known.has(id))
|
|
634
841
|
.map((id) => fallbackEntry(id, 'Added by hand in Settings', GROK_CONTEXT_WINDOW));
|
|
635
842
|
}
|
|
636
|
-
/**
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
843
|
+
/**
|
|
844
|
+
* Adopt the ids a caller reports as the served set, without a listing.
|
|
845
|
+
*
|
|
846
|
+
* {@link fetchCatalog} already derived the entries from the rows it read, and the
|
|
847
|
+
* controller reports the union of every account's ids back here; the entries it
|
|
848
|
+
* recorded are kept, and an id no entry describes gets a conservative row described by
|
|
849
|
+
* the cold-start seed. An id the caller does not report is dropped: the reported set is
|
|
850
|
+
* the endpoint's whole answer.
|
|
851
|
+
*
|
|
852
|
+
* @param ids - model ids the caller reports.
|
|
853
|
+
*/
|
|
854
|
+
discover(ids) {
|
|
855
|
+
const known = new Map((this.live ?? []).map((entry) => [entry.id, entry]));
|
|
856
|
+
this.live = ids.map((id) => known.get(id)
|
|
857
|
+
?? liveEntry({ id }, GROK_MODELS, GROK_CONTEXT_WINDOW, GROK_LIVE_DESCRIPTION));
|
|
643
858
|
}
|
|
644
859
|
/** The fast/lightspeed variant wire id for a base model, when discovery reported one. */
|
|
645
860
|
fastVariant(baseId) {
|
|
@@ -662,8 +877,6 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
662
877
|
return this.shippedCatalog();
|
|
663
878
|
}
|
|
664
879
|
/** Build and send one request for one model. */
|
|
665
|
-
/** The default context variant per base model, set during discovery. */
|
|
666
|
-
defaultContext = new Map();
|
|
667
880
|
/** Resolve a model id to the wire id the endpoint accepts (largest context). */
|
|
668
881
|
resolveModelId(model) {
|
|
669
882
|
return this.defaultContext.get(model) ?? model;
|
|
@@ -726,9 +939,18 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
726
939
|
};
|
|
727
940
|
}
|
|
728
941
|
/**
|
|
729
|
-
* Read the
|
|
730
|
-
*
|
|
731
|
-
*
|
|
942
|
+
* Read both of the endpoint's listings and adopt them as this route's catalog.
|
|
943
|
+
*
|
|
944
|
+
* This is the one place a model table is produced, and it is the endpoint's own answer:
|
|
945
|
+
* what the public list names is what the picker offers, and the account's CLI catalog
|
|
946
|
+
* states the capabilities and names the fast variants that list omits. The rows are also
|
|
947
|
+
* handed to {@link GrokAdapterOptions.saveListing}, so the next session starts from this
|
|
948
|
+
* table instead of asking again — the Settings "refresh models" action is what re-reads
|
|
949
|
+
* it, and a picker query never does.
|
|
950
|
+
*
|
|
951
|
+
* @param accessToken - bearer for the account.
|
|
952
|
+
* @param signal - optional cancellation.
|
|
953
|
+
* @returns the model ids now served, or undefined when the listing is unavailable.
|
|
732
954
|
*/
|
|
733
955
|
async fetchCatalog(accessToken, signal) {
|
|
734
956
|
if (!this.options.discovery)
|
|
@@ -743,56 +965,36 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
743
965
|
// A refused credential is reported by the card's account list; only a
|
|
744
966
|
// genuine discovery failure is worth a warning.
|
|
745
967
|
if (response.status !== 401 && response.status !== 403) {
|
|
746
|
-
this.options.onWarn?.(`xAI model discovery failed (HTTP ${response.status});
|
|
968
|
+
this.options.onWarn?.(`xAI model discovery failed (HTTP ${response.status}); keeping the last catalog`);
|
|
747
969
|
}
|
|
748
970
|
return undefined;
|
|
749
971
|
}
|
|
750
972
|
const payload = await response.json();
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
// Fast/lightspeed variants ride with their base model: they share the base's
|
|
758
|
-
// visibility switch and are reached by a picker button, not a separate row.
|
|
759
|
-
// Context variants (@500k) also fold into the base; the largest context wins.
|
|
760
|
-
this.fastVariants.clear();
|
|
761
|
-
this.defaultContext.clear();
|
|
762
|
-
for (const id of rawIds) {
|
|
763
|
-
this.fastVariants.noteListed(id);
|
|
764
|
-
}
|
|
765
|
-
this.noteWireVariants(rawIds);
|
|
766
|
-
const ids = rawIds
|
|
767
|
-
.map((id) => {
|
|
768
|
-
if (isFastVariant(id))
|
|
769
|
-
return this.fastVariants.pair(id) ?? baseModelOf(id);
|
|
770
|
-
const ctx = contextOf(id);
|
|
771
|
-
if (ctx !== undefined)
|
|
772
|
-
return stripContext(id);
|
|
773
|
-
return id;
|
|
774
|
-
})
|
|
775
|
-
.filter((id, index, list) => list.indexOf(id) === index);
|
|
776
|
-
if (ids.length === 0) {
|
|
777
|
-
this.options.onWarn?.('xAI model discovery returned an empty catalog; using the built-in catalog');
|
|
973
|
+
const publicRows = Array.isArray(payload.data) ? payload.data : [];
|
|
974
|
+
const cliRows = await this.readCliCatalog(fetchFn, signal, accessToken);
|
|
975
|
+
const listing = { public: publicRows, cli: cliRows };
|
|
976
|
+
const entries = this.adoptListing(listing);
|
|
977
|
+
if (entries === undefined) {
|
|
978
|
+
this.options.onWarn?.('xAI model discovery returned no usable catalog; keeping the last catalog');
|
|
778
979
|
return undefined;
|
|
779
980
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
981
|
+
this.live = entries;
|
|
982
|
+
// The cached table is the endpoint's answer, kept so the next session does not ask
|
|
983
|
+
// again. A cache failure never fails the read that produced it.
|
|
984
|
+
if (this.options.saveListing !== undefined) {
|
|
985
|
+
try {
|
|
986
|
+
await this.options.saveListing(listing);
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
this.options.onWarn?.(`model catalog cache write failed (${errorChain(error)})`);
|
|
990
|
+
}
|
|
788
991
|
}
|
|
789
|
-
|
|
790
|
-
return ids;
|
|
992
|
+
return entries.map((entry) => entry.id);
|
|
791
993
|
}
|
|
792
994
|
catch (error) {
|
|
793
995
|
if (signal?.aborted === true)
|
|
794
996
|
return undefined;
|
|
795
|
-
this.options.onWarn?.(`xAI model discovery failed;
|
|
997
|
+
this.options.onWarn?.(`xAI model discovery failed; keeping the last catalog (${errorChain(error)})`);
|
|
796
998
|
return undefined;
|
|
797
999
|
}
|
|
798
1000
|
}
|
|
@@ -818,57 +1020,34 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
818
1020
|
}
|
|
819
1021
|
}
|
|
820
1022
|
/**
|
|
821
|
-
* Read
|
|
1023
|
+
* Read the account's CLI catalog; a failure is an empty listing, not an error.
|
|
822
1024
|
*
|
|
823
1025
|
* The catalog answers only for the signed-in account's own bearer — an anonymous
|
|
824
|
-
* request is refused — and it, unlike the public `/models` list,
|
|
825
|
-
* fast variant.
|
|
1026
|
+
* request is refused — and it, unlike the public `/models` list, states per-model
|
|
1027
|
+
* context windows and reasoning ladders and names the account's fast variant. The rows
|
|
1028
|
+
* are returned verbatim so both the fresh catalog and the cached payload are derived
|
|
1029
|
+
* from the same parse.
|
|
826
1030
|
*
|
|
827
1031
|
* @param fetchFn - fetch implementation.
|
|
828
1032
|
* @param signal - caller cancellation.
|
|
829
1033
|
* @param accessToken - the account's bearer.
|
|
830
|
-
* @returns the
|
|
1034
|
+
* @returns the decoded rows, or an empty list when the catalog is unavailable.
|
|
831
1035
|
*/
|
|
832
1036
|
async readCliCatalog(fetchFn, signal, accessToken) {
|
|
833
|
-
const result = new Map();
|
|
834
|
-
const ids = [];
|
|
835
1037
|
try {
|
|
836
1038
|
const response = await fetchFn(GROK_CLI_MODELS_URL, {
|
|
837
1039
|
headers: attributedHeaders({ 'authorization': `Bearer ${accessToken}` }),
|
|
838
1040
|
...signal === undefined ? {} : { signal },
|
|
839
1041
|
});
|
|
840
1042
|
if (!response.ok)
|
|
841
|
-
return
|
|
1043
|
+
return [];
|
|
842
1044
|
const payload = await response.json();
|
|
843
|
-
|
|
844
|
-
const id = typeof entry.id === 'string' && entry.id.length > 0 ? entry.id : undefined;
|
|
845
|
-
if (id === undefined)
|
|
846
|
-
continue;
|
|
847
|
-
ids.push(id);
|
|
848
|
-
const contextWindow = typeof entry.context_window === 'number' && Number.isFinite(entry.context_window)
|
|
849
|
-
? entry.context_window
|
|
850
|
-
: undefined;
|
|
851
|
-
const supportsEffort = entry.supports_reasoning_effort === true;
|
|
852
|
-
const efforts = supportsEffort
|
|
853
|
-
? (entry.reasoning_efforts ?? [])
|
|
854
|
-
.map((effort) => effort.value)
|
|
855
|
-
.filter((value) => typeof value === 'string' && value.length > 0)
|
|
856
|
-
: [];
|
|
857
|
-
const defaultEffort = supportsEffort && typeof entry.reasoning_effort === 'string'
|
|
858
|
-
&& efforts.includes(entry.reasoning_effort)
|
|
859
|
-
? entry.reasoning_effort
|
|
860
|
-
: undefined;
|
|
861
|
-
result.set(id, {
|
|
862
|
-
...contextWindow === undefined ? {} : { contextWindow },
|
|
863
|
-
...efforts.length === 0 ? {} : { reasoningEfforts: efforts },
|
|
864
|
-
...defaultEffort === undefined ? {} : { defaultEffort },
|
|
865
|
-
});
|
|
866
|
-
}
|
|
1045
|
+
return Array.isArray(payload.data) ? payload.data : [];
|
|
867
1046
|
}
|
|
868
1047
|
catch {
|
|
869
1048
|
// Capability metadata is a bonus; the id list is what matters.
|
|
1049
|
+
return [];
|
|
870
1050
|
}
|
|
871
|
-
return { metadata: result, ids };
|
|
872
1051
|
}
|
|
873
1052
|
}
|
|
874
1053
|
/**
|
|
@@ -960,8 +1139,13 @@ export function createGrokProvider(options) {
|
|
|
960
1139
|
const read = await readGrokUsage({ accessToken: token.accessToken, fetchFn: options.discoveryFetchFn ?? proxiedFetch });
|
|
961
1140
|
return read === undefined ? [] : read.windows;
|
|
962
1141
|
},
|
|
1142
|
+
...options.cachedListing === undefined ? {} : { cachedListing: options.cachedListing },
|
|
1143
|
+
...options.saveListing === undefined ? {} : { saveListing: options.saveListing },
|
|
963
1144
|
...options.discoveryFetchFn === undefined ? {} : { fetchFn: options.discoveryFetchFn },
|
|
964
1145
|
});
|
|
1146
|
+
// Read the cached table now rather than at the first picker query: the Settings page
|
|
1147
|
+
// reads the catalog synchronously, so a request-free start must already have it.
|
|
1148
|
+
void adapter.hydrate().catch(() => undefined);
|
|
965
1149
|
const spec = {
|
|
966
1150
|
id: GROK_PROVIDER_ID,
|
|
967
1151
|
displayName: GROK_PROVIDER_NAME,
|
|
@@ -26,6 +26,16 @@ export interface KimiProviderOptions {
|
|
|
26
26
|
defaultModel?: string;
|
|
27
27
|
/** Extra model ids published without shipped metadata. */
|
|
28
28
|
extraModels?: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* The endpoint's last live payload, from this plugin's cache.
|
|
31
|
+
*
|
|
32
|
+
* The cached table is what the picker shows before any request: the endpoint's own
|
|
33
|
+
* answer from the last read, so a release does not need a plugin update and a refresh
|
|
34
|
+
* does not need a request.
|
|
35
|
+
*/
|
|
36
|
+
cachedListing?: () => Promise<unknown>;
|
|
37
|
+
/** Record what a live listing just produced, replacing the cached table. */
|
|
38
|
+
saveListing?: (payload: unknown) => Promise<void>;
|
|
29
39
|
/** Shared model-visibility preferences. */
|
|
30
40
|
preferences: ModelPreferences;
|
|
31
41
|
/** Resolve the attachment service per request. */
|
|
@@ -99,16 +99,21 @@ export function createKimiProvider(options) {
|
|
|
99
99
|
hiddenModels: () => options.preferences.hiddenIds(KIMI_PROVIDER_ID),
|
|
100
100
|
userModels: () => options.preferences.extraIds(KIMI_PROVIDER_ID),
|
|
101
101
|
modelOrder: () => options.preferences.orderOf(KIMI_PROVIDER_ID),
|
|
102
|
+
defaultModel: options.defaultModel ?? KIMI_DEFAULT_MODEL,
|
|
103
|
+
...options.cachedListing === undefined ? {} : { cachedListing: options.cachedListing },
|
|
104
|
+
...options.saveListing === undefined ? {} : { saveListing: options.saveListing },
|
|
102
105
|
...options.extraModels === undefined ? {} : { extraModels: options.extraModels },
|
|
103
106
|
fetchFn,
|
|
104
107
|
});
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
// Read the cached table now rather than at the first picker query: the Settings page
|
|
109
|
+
// reads the catalog synchronously, so a request-free start must already have it.
|
|
110
|
+
void adapter.hydrate().catch(() => undefined);
|
|
111
|
+
const spec = { id: KIMI_PROVIDER_ID,
|
|
107
112
|
displayName: KIMI_PROVIDER_NAME,
|
|
108
113
|
codeUrl: KIMI_CODE_URL,
|
|
109
114
|
credentialFile: options.credentialFile,
|
|
110
115
|
discovery: options.discovery,
|
|
111
|
-
defaultModel: KIMI_DEFAULT_MODEL,
|
|
116
|
+
defaultModel: options.defaultModel ?? KIMI_DEFAULT_MODEL,
|
|
112
117
|
catalog: () => adapter.catalogEntries(),
|
|
113
118
|
catalogOwn: () => adapter.ownCatalogEntries(),
|
|
114
119
|
fallbackContextWindow: KIMI_CONTEXT_WINDOW,
|