dsh-plugin-subscription-oauth 0.8.13 → 0.8.15

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.
@@ -116,8 +116,86 @@ interface GrokCliMetadata {
116
116
  export declare function isImageModel(id: string): boolean;
117
117
  /** True when a model id names a fast/lightspeed variant of a base model. */
118
118
  export declare function isFastVariant(id: string): boolean;
119
- /** The base model id a fast variant belongs to (`grok-4.7-fast` → `grok-4.7`). */
119
+ /**
120
+ * Every prefix of one model id, longest first, down to its first token.
121
+ *
122
+ * `grok-4.7-build-fast` → [`grok-4.7-build`, `grok-4.7`, `grok`]. A fast variant pairs
123
+ * with the longest prefix that is itself a listed model id, which is how
124
+ * `grok-4.7-build-fast` lands on `grok-4.7` (its own prefix `grok-4.7-build` is not a
125
+ * model) instead of inventing a base that no catalog row serves.
126
+ *
127
+ * @param id - model id.
128
+ * @returns the candidate bases, longest first.
129
+ */
130
+ export declare function baseCandidatesOf(id: string): readonly string[];
131
+ /**
132
+ * The base model id a fast variant belongs to, derived only from its own name.
133
+ *
134
+ * The fast token is removed, then any trailing generation qualifier (`build`, `beta`,
135
+ * `rc`, …) left behind by that removal, then any `@Nk` context suffix. What remains is
136
+ * the base (`grok-4.7-build-fast@500k` → `grok-4.7`, `kimi-for-coding-highspeed` →
137
+ * `kimi-for-coding`). Whether the base is a real catalog row is the index's job, not
138
+ * this function's.
139
+ */
120
140
  export declare function baseModelOf(id: string): string;
141
+ /**
142
+ * The fast variant of one picker row, from either side of the pair.
143
+ *
144
+ * Shared by every route that discovers fast ids, so the composer's quick-mode button
145
+ * behaves the same wherever a fast model is identifiable: it finds the pair when asked
146
+ * about the base row, and when asked about the fast id itself — which is what the
147
+ * selection becomes once the mode is on — so it can switch back off again.
148
+ */
149
+ export declare class FastVariantIndex {
150
+ private readonly byBase;
151
+ private readonly rowOfVariant;
152
+ private readonly knownIds;
153
+ /** Forget every pair and every known id, before re-reading a catalog. */
154
+ clear(): void;
155
+ /**
156
+ * Record one id the catalog listed.
157
+ *
158
+ * Listed ids are what a variant may pair with: the pair is the longest listed prefix
159
+ * of the variant's own name, so nothing is invented that no row actually serves.
160
+ *
161
+ * @param id - a wire id from a catalog.
162
+ */
163
+ noteListed(id: string): void;
164
+ /**
165
+ * Pair one fast id with its base, purely from its own name.
166
+ *
167
+ * Every listed prefix of the id is a candidate base, longest first; the first one
168
+ * that is itself a listed model wins. No curated alias or hardcoded map is needed:
169
+ * a provider that names its variants after the base model pairs them automatically.
170
+ *
171
+ * @param id - a wire id the catalog named.
172
+ * @returns the base id, or undefined when no listed prefix names one.
173
+ */
174
+ pair(id: string): string | undefined;
175
+ /**
176
+ * Record one id, if it names a fast variant.
177
+ *
178
+ * The first variant named for a row wins: a catalog that lists two spellings of the
179
+ * same variant (`kimi-for-coding-highspeed` and `kimi-k2.7-code-highspeed`) offers
180
+ * one button, and the earlier entry is the more specific one.
181
+ *
182
+ * @param id - a wire id from a catalog.
183
+ * @param rowOf - maps a base id to the picker row that serves it, when the catalog
184
+ * spells the base differently from the variant's own prefix.
185
+ */
186
+ note(id: string, rowOf?: (base: string) => string | undefined): void;
187
+ /** The fast variant's wire id for one picker row, when the catalog named one. */
188
+ variantOf(baseId: string): string | undefined;
189
+ /** The pair one picker id belongs to, from either side. */
190
+ pairOf(id: string): {
191
+ baseId: string;
192
+ wireId: string;
193
+ } | undefined;
194
+ }
195
+ /** Extract the context size from a model id's `@Nk` suffix, or undefined. */
196
+ export declare function contextOf(id: string): number | undefined;
197
+ /** Strip the `@Nk` context suffix from a model id. */
198
+ export declare function stripContext(id: string): string;
121
199
  /** Constructor dependencies for {@link GrokAdapter}. */
122
200
  export interface GrokAdapterOptions {
123
201
  /** API base; `<base>/responses` and `<base>/models`. */
@@ -190,6 +268,11 @@ export declare class GrokAdapter extends LlmAdapter {
190
268
  discover(ids: readonly string[], metadata?: ReadonlyMap<string, GrokCliMetadata>): void;
191
269
  /** The fast/lightspeed variant wire id for a base model, when discovery reported one. */
192
270
  fastVariant(baseId: string): string | undefined;
271
+ /** The fast pair one picker id belongs to, from either side. */
272
+ fastPairOf(id: string): {
273
+ baseId: string;
274
+ wireId: string;
275
+ } | undefined;
193
276
  /** The image-generation model id, when discovery reported one. */
194
277
  imageModelId(): string | undefined;
195
278
  /** The curated catalog, for the Settings page's model list. */
@@ -197,6 +280,12 @@ export declare class GrokAdapter extends LlmAdapter {
197
280
  /** What this route itself serves, without the ids added by hand. */
198
281
  ownCatalogEntries(): readonly CatalogEntry[];
199
282
  /** Build and send one request for one model. */
283
+ /** The default context variant per base model, set during discovery. */
284
+ private defaultContext;
285
+ /** Resolve a model id to the wire id the endpoint accepts (largest context). */
286
+ resolveModelId(model: string): string;
287
+ /** Record one default-context mapping. */
288
+ private noteContextVariant;
200
289
  private request;
201
290
  /** Resolve image blocks through the attachment service, when it is present. */
202
291
  private imageResolver;
@@ -206,8 +295,25 @@ export declare class GrokAdapter extends LlmAdapter {
206
295
  * @returns the model ids, or undefined when discovery is off or unavailable.
207
296
  */
208
297
  fetchCatalog(accessToken: string, signal?: AbortSignal): Promise<readonly string[] | undefined>;
209
- /** Read per-model capabilities from the CLI catalog; failures are silent. */
210
- private fetchCliMetadata;
298
+ /**
299
+ * Fold the fast and context variants of one id list into their base models.
300
+ *
301
+ * @param ids - wire ids to classify.
302
+ */
303
+ private noteWireVariants;
304
+ /**
305
+ * Read per-model capabilities from the CLI catalog; failures are silent.
306
+ *
307
+ * The catalog answers only for the signed-in account's own bearer — an anonymous
308
+ * request is refused — and it, unlike the public `/models` list, names the account's
309
+ * fast variant.
310
+ *
311
+ * @param fetchFn - fetch implementation.
312
+ * @param signal - caller cancellation.
313
+ * @param accessToken - the account's bearer.
314
+ * @returns the capability metadata and every id the catalog named.
315
+ */
316
+ private readCliCatalog;
211
317
  }
212
318
  /** Everything the `grok` route needs from the plugin configuration. */
213
319
  export interface GrokProviderOptions {
@@ -268,15 +268,152 @@ const IMAGE_MODEL = /(?:^|[-_/])(?:image|imagine|draw|pic|photo)(?:[-_/]|$)/i;
268
268
  export function isImageModel(id) {
269
269
  return IMAGE_MODEL.test(id);
270
270
  }
271
- /** Model ids that are the fast/lightspeed variant of a base model, matched per base. */
272
- const FAST_VARIANT = /(?:^|[-_])(?:fast|lightspeed|turbo|quick)$/i;
271
+ /**
272
+ * Model ids that are the fast/lightspeed variant of a base model, matched per base.
273
+ *
274
+ * The vocabulary is the providers' own: xAI says `fast` (`grok-4.7-build-fast`), the
275
+ * Claude/Codex world says `lightspeed` or `turbo`, and Kimi says `highspeed`
276
+ * (`kimi-for-coding-highspeed`). A word has to stand on its own, so `highspeed` never
277
+ * matches inside another word.
278
+ */
279
+ const FAST_VARIANT = /(?:^|[-_@])(?:fast|lightspeed|turbo|quick|highspeed)(?=[-_@]|$)/i;
273
280
  /** True when a model id names a fast/lightspeed variant of a base model. */
274
281
  export function isFastVariant(id) {
275
282
  return FAST_VARIANT.test(id);
276
283
  }
277
- /** The base model id a fast variant belongs to (`grok-4.7-fast` → `grok-4.7`). */
284
+ /**
285
+ * Every prefix of one model id, longest first, down to its first token.
286
+ *
287
+ * `grok-4.7-build-fast` → [`grok-4.7-build`, `grok-4.7`, `grok`]. A fast variant pairs
288
+ * with the longest prefix that is itself a listed model id, which is how
289
+ * `grok-4.7-build-fast` lands on `grok-4.7` (its own prefix `grok-4.7-build` is not a
290
+ * model) instead of inventing a base that no catalog row serves.
291
+ *
292
+ * @param id - model id.
293
+ * @returns the candidate bases, longest first.
294
+ */
295
+ export function baseCandidatesOf(id) {
296
+ const out = [];
297
+ let at = id.length;
298
+ while (at > 0) {
299
+ at = id.lastIndexOf('-', at - 1);
300
+ if (at <= 0)
301
+ break;
302
+ out.push(id.slice(0, at));
303
+ }
304
+ return out;
305
+ }
306
+ /**
307
+ * The base model id a fast variant belongs to, derived only from its own name.
308
+ *
309
+ * The fast token is removed, then any trailing generation qualifier (`build`, `beta`,
310
+ * `rc`, …) left behind by that removal, then any `@Nk` context suffix. What remains is
311
+ * the base (`grok-4.7-build-fast@500k` → `grok-4.7`, `kimi-for-coding-highspeed` →
312
+ * `kimi-for-coding`). Whether the base is a real catalog row is the index's job, not
313
+ * this function's.
314
+ */
278
315
  export function baseModelOf(id) {
279
- return id.replace(FAST_VARIANT, '');
316
+ return id
317
+ .replace(FAST_VARIANT, '')
318
+ .replace(/@\d+k$/i, '')
319
+ .replace(/[-_]$/, '')
320
+ .replace(/-(?:build|beta|alpha|rc|stable)$/i, '')
321
+ .replace(/[-_]$/, '');
322
+ }
323
+ /**
324
+ * The fast variant of one picker row, from either side of the pair.
325
+ *
326
+ * Shared by every route that discovers fast ids, so the composer's quick-mode button
327
+ * behaves the same wherever a fast model is identifiable: it finds the pair when asked
328
+ * about the base row, and when asked about the fast id itself — which is what the
329
+ * selection becomes once the mode is on — so it can switch back off again.
330
+ */
331
+ export class FastVariantIndex {
332
+ byBase = new Map();
333
+ rowOfVariant = new Map();
334
+ knownIds = new Set();
335
+ /** Forget every pair and every known id, before re-reading a catalog. */
336
+ clear() {
337
+ this.byBase.clear();
338
+ this.rowOfVariant.clear();
339
+ this.knownIds.clear();
340
+ }
341
+ /**
342
+ * Record one id the catalog listed.
343
+ *
344
+ * Listed ids are what a variant may pair with: the pair is the longest listed prefix
345
+ * of the variant's own name, so nothing is invented that no row actually serves.
346
+ *
347
+ * @param id - a wire id from a catalog.
348
+ */
349
+ noteListed(id) {
350
+ this.knownIds.add(id);
351
+ }
352
+ /**
353
+ * Pair one fast id with its base, purely from its own name.
354
+ *
355
+ * Every listed prefix of the id is a candidate base, longest first; the first one
356
+ * that is itself a listed model wins. No curated alias or hardcoded map is needed:
357
+ * a provider that names its variants after the base model pairs them automatically.
358
+ *
359
+ * @param id - a wire id the catalog named.
360
+ * @returns the base id, or undefined when no listed prefix names one.
361
+ */
362
+ pair(id) {
363
+ if (!isFastVariant(id))
364
+ return undefined;
365
+ for (const base of baseCandidatesOf(id)) {
366
+ if (this.knownIds.has(base))
367
+ return base;
368
+ }
369
+ return undefined;
370
+ }
371
+ /**
372
+ * Record one id, if it names a fast variant.
373
+ *
374
+ * The first variant named for a row wins: a catalog that lists two spellings of the
375
+ * same variant (`kimi-for-coding-highspeed` and `kimi-k2.7-code-highspeed`) offers
376
+ * one button, and the earlier entry is the more specific one.
377
+ *
378
+ * @param id - a wire id from a catalog.
379
+ * @param rowOf - maps a base id to the picker row that serves it, when the catalog
380
+ * spells the base differently from the variant's own prefix.
381
+ */
382
+ note(id, rowOf) {
383
+ if (!isFastVariant(id))
384
+ return;
385
+ const base = baseModelOf(id);
386
+ if (base.length === 0)
387
+ return;
388
+ const row = rowOf?.(base) ?? base;
389
+ if (!this.byBase.has(row))
390
+ this.byBase.set(row, id);
391
+ if (!this.rowOfVariant.has(id))
392
+ this.rowOfVariant.set(id, row);
393
+ }
394
+ /** The fast variant's wire id for one picker row, when the catalog named one. */
395
+ variantOf(baseId) {
396
+ return this.byBase.get(baseId);
397
+ }
398
+ /** The pair one picker id belongs to, from either side. */
399
+ pairOf(id) {
400
+ const direct = this.byBase.get(id);
401
+ if (direct !== undefined)
402
+ return { baseId: id, wireId: direct };
403
+ const row = this.rowOfVariant.get(id);
404
+ return row === undefined ? undefined : { baseId: row, wireId: id };
405
+ }
406
+ }
407
+ /** A context-size suffix like `@500k` on a model id. */
408
+ const CONTEXT_SUFFIX = /@(\d+)k$/i;
409
+ /** Extract the context size from a model id's `@Nk` suffix, or undefined. */
410
+ export function contextOf(id) {
411
+ const match = CONTEXT_SUFFIX.exec(id);
412
+ return match ? parseInt(match[1], 10) : undefined;
413
+ }
414
+ /** Strip the `@Nk` context suffix from a model id. */
415
+ export function stripContext(id) {
416
+ return id.replace(CONTEXT_SUFFIX, '');
280
417
  }
281
418
  /** Grok wire adapter: one instance serves the `grok` provider route. */
282
419
  export class GrokAdapter extends LlmAdapter {
@@ -286,7 +423,7 @@ export class GrokAdapter extends LlmAdapter {
286
423
  /** Capability metadata from the CLI catalog, keyed by model id. */
287
424
  metadata = new Map();
288
425
  /** Fast-variant wire id per base model, for the picker's quick-mode button. */
289
- fastVariants = new Map();
426
+ fastVariants = new FastVariantIndex();
290
427
  /** Image-generation model id, when discovery reported one. */
291
428
  imageModel;
292
429
  /** Ids the user added by hand, cached so the sync catalog can name them. */
@@ -309,15 +446,25 @@ export class GrokAdapter extends LlmAdapter {
309
446
  return [];
310
447
  await this.syncUserModels();
311
448
  const hidden = await this.hidden();
312
- // Image models are never picker rows: they are agent-called, always on.
449
+ // A fast variant is not a picker row, but the host validates a GUI selection
450
+ // against the exact list `listModels` returns, so the variant must appear there
451
+ // or selecting it is refused with `session/model-unavailable`. The picker's
452
+ // menu collapses entries that share one `group` value into a single choice,
453
+ // so the variant rides with its base id as the group: the host can name and
454
+ // select it, and the picker still shows one row per model.
313
455
  const imageless = this.catalog().filter((entry) => !isImageModel(entry.id));
314
- return resolveCatalog(imageless, [], hidden).entries.map((entry) => ({
315
- provider: GROK_PROVIDER_ID,
316
- id: entry.id,
317
- name: entry.name,
318
- ...entry.description === undefined ? {} : { description: entry.description },
319
- ...entry.inputModalities === undefined ? {} : { inputModalities: entry.inputModalities },
320
- }));
456
+ const listed = resolveCatalog(imageless, [], hidden).entries.flatMap((entry) => {
457
+ const fast = this.fastVariants.variantOf(entry.id);
458
+ const base = {
459
+ provider: GROK_PROVIDER_ID,
460
+ id: entry.id,
461
+ name: entry.name,
462
+ ...entry.description === undefined ? {} : { description: entry.description },
463
+ ...entry.inputModalities === undefined ? {} : { inputModalities: entry.inputModalities },
464
+ };
465
+ return fast === undefined ? [base] : [base, { provider: GROK_PROVIDER_ID, id: fast, name: entry.name, group: entry.id, description: 'Fast variant', ...entry.inputModalities === undefined ? {} : { inputModalities: entry.inputModalities } }];
466
+ });
467
+ return listed;
321
468
  }
322
469
  async hidden() {
323
470
  if (this.options.hiddenModels === undefined)
@@ -332,7 +479,12 @@ export class GrokAdapter extends LlmAdapter {
332
479
  }
333
480
  async resolveModel(_provider, model) {
334
481
  await this.syncUserModels();
335
- const entry = findEntry(this.catalog(), model);
482
+ // A fast variant is not a catalog row of its own: it answers with its base model's
483
+ // metadata — the same 500k window, name and ladder the picker shows — while the id
484
+ // stays the fast wire id the request must send.
485
+ const pair = this.fastPairOf(model);
486
+ const entry = findEntry(this.catalog(), model)
487
+ ?? (pair === undefined ? undefined : findEntry(this.catalog(), pair.baseId));
336
488
  if (entry === undefined) {
337
489
  const template = findEntry(GROK_MODELS, 'grok-4.6');
338
490
  const reasoning = template === undefined ? undefined : reasoningInfo(template);
@@ -348,7 +500,7 @@ export class GrokAdapter extends LlmAdapter {
348
500
  const reasoning = reasoningInfo(entry);
349
501
  return {
350
502
  provider: GROK_PROVIDER_ID,
351
- id: entry.id,
503
+ id: model,
352
504
  name: entry.name,
353
505
  ...entry.description === undefined ? {} : { description: entry.description },
354
506
  ...entry.inputModalities === undefined ? {} : { inputModalities: entry.inputModalities },
@@ -358,7 +510,9 @@ export class GrokAdapter extends LlmAdapter {
358
510
  };
359
511
  }
360
512
  stream(options) {
361
- return streamProviderResponses(options, {
513
+ // Resolve @Nk context suffixes to the largest variant the endpoint offers.
514
+ const model = this.resolveModelId(options.model);
515
+ return streamProviderResponses({ ...options, model }, {
362
516
  label: 'xAI API',
363
517
  tokens: this.options.tokens,
364
518
  request: (call, model, accessToken, signal) => this.request(call, model, accessToken, signal),
@@ -409,14 +563,17 @@ export class GrokAdapter extends LlmAdapter {
409
563
  ...meta?.defaultEffort === undefined ? {} : { defaultEffort: meta.defaultEffort },
410
564
  });
411
565
  }
412
- // The seeds also pick up live metadata (context window, reasoning ladder).
566
+ // The seeds also pick up live metadata (reasoning ladder, default effort). The
567
+ // curated context window is never narrowed by a live figure: the CLI catalog states
568
+ // 256k for `grok-4.7`, which serves 500k, and a live read may only widen what this
569
+ // build ships.
413
570
  const seeded = GROK_MODELS.map((entry) => {
414
571
  const meta = this.metadata.get(entry.id);
415
572
  if (meta === undefined)
416
573
  return entry;
417
574
  return {
418
575
  ...entry,
419
- contextWindow: meta.contextWindow ?? entry.contextWindow,
576
+ contextWindow: Math.max(entry.contextWindow, meta.contextWindow ?? 0),
420
577
  ...meta.reasoningEfforts === undefined ? {} : { reasoningEfforts: meta.reasoningEfforts },
421
578
  ...meta.defaultEffort === undefined ? {} : { defaultEffort: meta.defaultEffort },
422
579
  };
@@ -459,7 +616,11 @@ export class GrokAdapter extends LlmAdapter {
459
616
  }
460
617
  /** The fast/lightspeed variant wire id for a base model, when discovery reported one. */
461
618
  fastVariant(baseId) {
462
- return this.fastVariants.get(baseId);
619
+ return this.fastVariants.variantOf(baseId);
620
+ }
621
+ /** The fast pair one picker id belongs to, from either side. */
622
+ fastPairOf(id) {
623
+ return this.fastVariants.pairOf(id);
463
624
  }
464
625
  /** The image-generation model id, when discovery reported one. */
465
626
  imageModelId() {
@@ -474,6 +635,22 @@ export class GrokAdapter extends LlmAdapter {
474
635
  return this.shippedCatalog();
475
636
  }
476
637
  /** Build and send one request for one model. */
638
+ /** The default context variant per base model, set during discovery. */
639
+ defaultContext = new Map();
640
+ /** Resolve a model id to the wire id the endpoint accepts (largest context). */
641
+ resolveModelId(model) {
642
+ return this.defaultContext.get(model) ?? model;
643
+ }
644
+ /** Record one default-context mapping. */
645
+ noteContextVariant(base, context, wireId) {
646
+ const existing = this.defaultContext.get(base);
647
+ if (existing !== undefined) {
648
+ const existingCtx = contextOf(existing);
649
+ if (existingCtx !== undefined && existingCtx >= context)
650
+ return;
651
+ }
652
+ this.defaultContext.set(base, wireId);
653
+ }
477
654
  async request(options, model, accessToken, signal) {
478
655
  const resolved = await toResponsesInput(options.messages, options.system, this.imageResolver(signal));
479
656
  const body = {
@@ -552,24 +729,37 @@ export class GrokAdapter extends LlmAdapter {
552
729
  const rawIds = allIds.filter((id) => !NON_CHAT_ID.test(id));
553
730
  // Fast/lightspeed variants ride with their base model: they share the base's
554
731
  // visibility switch and are reached by a picker button, not a separate row.
732
+ // Context variants (@500k) also fold into the base; the largest context wins.
555
733
  this.fastVariants.clear();
734
+ this.defaultContext.clear();
556
735
  for (const id of rawIds) {
557
- if (!isFastVariant(id))
558
- continue;
559
- const base = baseModelOf(id);
560
- if (!this.fastVariants.has(base))
561
- this.fastVariants.set(base, id);
736
+ this.fastVariants.noteListed(id);
562
737
  }
738
+ this.noteWireVariants(rawIds);
563
739
  const ids = rawIds
564
- .map((id) => (isFastVariant(id) ? baseModelOf(id) : id))
740
+ .map((id) => {
741
+ if (isFastVariant(id))
742
+ return this.fastVariants.pair(id) ?? baseModelOf(id);
743
+ const ctx = contextOf(id);
744
+ if (ctx !== undefined)
745
+ return stripContext(id);
746
+ return id;
747
+ })
565
748
  .filter((id, index, list) => list.indexOf(id) === index);
566
749
  if (ids.length === 0) {
567
750
  this.options.onWarn?.('xAI model discovery returned an empty catalog; using the built-in catalog');
568
751
  return undefined;
569
752
  }
570
- const metadata = await this.fetchCliMetadata(fetchFn, signal);
571
- for (const [id, meta] of metadata)
753
+ const cli = await this.readCliCatalog(fetchFn, signal, accessToken);
754
+ for (const [id, meta] of cli.metadata)
572
755
  this.metadata.set(id, meta);
756
+ // The account's own CLI catalog is the only surface that names a fast variant:
757
+ // the public `/models` list omits `grok-4.7-build-fast` entirely. Its ids fold
758
+ // into the same base rows, and are never added as picker rows of their own.
759
+ for (const id of cli.ids) {
760
+ this.fastVariants.noteListed(id);
761
+ }
762
+ this.noteWireVariants(cli.ids);
573
763
  return ids;
574
764
  }
575
765
  catch (error) {
@@ -579,21 +769,55 @@ export class GrokAdapter extends LlmAdapter {
579
769
  return undefined;
580
770
  }
581
771
  }
582
- /** Read per-model capabilities from the CLI catalog; failures are silent. */
583
- async fetchCliMetadata(fetchFn, signal) {
772
+ /**
773
+ * Fold the fast and context variants of one id list into their base models.
774
+ *
775
+ * @param ids - wire ids to classify.
776
+ */
777
+ noteWireVariants(ids) {
778
+ for (const id of ids) {
779
+ if (NON_CHAT_ID.test(id) || isImageModel(id))
780
+ continue;
781
+ if (isFastVariant(id)) {
782
+ // Pair with the longest listed prefix, not a derived string: the prefix is a
783
+ // model the account actually serves, so the button always targets a real row.
784
+ const base = this.fastVariants.pair(id);
785
+ this.fastVariants.note(id, () => base);
786
+ continue;
787
+ }
788
+ const ctx = contextOf(id);
789
+ if (ctx !== undefined)
790
+ this.noteContextVariant(stripContext(id), ctx, id);
791
+ }
792
+ }
793
+ /**
794
+ * Read per-model capabilities from the CLI catalog; failures are silent.
795
+ *
796
+ * The catalog answers only for the signed-in account's own bearer — an anonymous
797
+ * request is refused — and it, unlike the public `/models` list, names the account's
798
+ * fast variant.
799
+ *
800
+ * @param fetchFn - fetch implementation.
801
+ * @param signal - caller cancellation.
802
+ * @param accessToken - the account's bearer.
803
+ * @returns the capability metadata and every id the catalog named.
804
+ */
805
+ async readCliCatalog(fetchFn, signal, accessToken) {
584
806
  const result = new Map();
807
+ const ids = [];
585
808
  try {
586
809
  const response = await fetchFn(GROK_CLI_MODELS_URL, {
587
- headers: attributedHeaders({ 'authorization': 'Bearer anonymous' }),
810
+ headers: attributedHeaders({ 'authorization': `Bearer ${accessToken}` }),
588
811
  ...signal === undefined ? {} : { signal },
589
812
  });
590
813
  if (!response.ok)
591
- return result;
814
+ return { metadata: result, ids };
592
815
  const payload = await response.json();
593
816
  for (const entry of payload.data ?? []) {
594
817
  const id = typeof entry.id === 'string' && entry.id.length > 0 ? entry.id : undefined;
595
818
  if (id === undefined)
596
819
  continue;
820
+ ids.push(id);
597
821
  const contextWindow = typeof entry.context_window === 'number' && Number.isFinite(entry.context_window)
598
822
  ? entry.context_window
599
823
  : undefined;
@@ -617,7 +841,7 @@ export class GrokAdapter extends LlmAdapter {
617
841
  catch {
618
842
  // Capability metadata is a bonus; the id list is what matters.
619
843
  }
620
- return result;
844
+ return { metadata: result, ids };
621
845
  }
622
846
  }
623
847
  /**
@@ -759,10 +983,10 @@ export function createGrokProvider(options) {
759
983
  usage: (force) => controller.usage(force),
760
984
  reload: () => controller.reload(),
761
985
  modelVariants: (id) => {
762
- const fast = adapter.fastVariant(id);
986
+ const pair = adapter.fastPairOf(id);
763
987
  const image = adapter.imageModelId();
764
988
  return {
765
- ...fast === undefined ? {} : { fast },
989
+ ...pair === undefined ? {} : { fast: pair.wireId, fastBase: pair.baseId },
766
990
  ...image === undefined ? {} : { image },
767
991
  };
768
992
  },
@@ -119,6 +119,7 @@ export function createKimiProvider(options) {
119
119
  { key: 'credentialFile', value: options.credentialFile },
120
120
  ],
121
121
  applyDiscovery: (ids) => { adapter.discover(ids); },
122
+ fastVariant: (baseId) => adapter.fastVariant(baseId),
122
123
  };
123
124
  const controller = new ProviderController({
124
125
  spec,
@@ -149,5 +150,13 @@ export function createKimiProvider(options) {
149
150
  removeModel: (id) => controller.removeModel(id),
150
151
  usage: (force) => controller.usage(force),
151
152
  reload: () => controller.reload(),
153
+ modelVariants: (id) => {
154
+ const pair = adapter.fastPairOf(id);
155
+ const image = adapter.imageModelId();
156
+ return {
157
+ ...pair === undefined ? {} : { fast: pair.wireId, fastBase: pair.baseId },
158
+ ...image === undefined ? {} : { image },
159
+ };
160
+ },
152
161
  };
153
162
  }
@@ -102,11 +102,35 @@ export declare class KimiAdapter extends LlmAdapter {
102
102
  private readonly options;
103
103
  /** Live catalog ids seen this session, used to accept renamed models. */
104
104
  private discovered;
105
+ /** Image-generation model id, when discovery reported one. */
106
+ private imageModel;
107
+ /**
108
+ * Fast-variant wire id per picker row, for the composer's quick-mode button.
109
+ *
110
+ * Seeded from the curated aliases — `kimi-for-coding-highspeed` is one — so the button
111
+ * exists before any discovery run, and then updated from the live catalog, which is
112
+ * the authority on what this account actually serves.
113
+ */
114
+ private readonly fastVariants;
105
115
  /** Ids the user added by hand, cached so the sync catalog can name them. */
106
116
  private userAddedIds;
107
117
  /** Tools already reported as flattened, so each is stated once. */
108
118
  private readonly flattenedTools;
109
119
  constructor(options: KimiAdapterOptions);
120
+ /**
121
+ * Record the fast variants the curated catalog already names as aliases.
122
+ *
123
+ * The alias is the recognition: `kimi-for-coding-highspeed` is stated as an accepted
124
+ * id of `kimi-for-coding`, so the route can offer the quick-mode button without
125
+ * waiting for a live list that may never name it. A row a live catalog already paired
126
+ * keeps that pairing, so this only fills the gaps.
127
+ */
128
+ private seedCuratedFastVariants;
129
+ /** The fast pair one picker id belongs to, from either side. */
130
+ fastPairOf(id: string): {
131
+ baseId: string;
132
+ wireId: string;
133
+ } | undefined;
110
134
  /**
111
135
  * Tool schemas in the shape Moonshot accepts: no `$ref`, and under its size
112
136
  * ceiling. A declaration too large to send keeps its arguments, and that is
@@ -116,6 +140,10 @@ export declare class KimiAdapter extends LlmAdapter {
116
140
  providerInfo(provider: string): LlmProviderInfo;
117
141
  providerRetryPolicy(): undefined;
118
142
  listModels(_provider: string): Promise<readonly LlmModelInfo[]>;
143
+ /** The image-generation model id, when discovery reported one. */
144
+ imageModelId(): string | undefined;
145
+ /** The fast/lightspeed variant wire id for a picker row. */
146
+ fastVariant(baseId: string): string | undefined;
119
147
  /** The ids the picker must currently omit. */
120
148
  private hidden;
121
149
  resolveModel(_provider: string, model: string): Promise<LlmResolvedModelInfo>;