dsh-plugin-subscriptions 0.2.0 → 0.3.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.md CHANGED
@@ -14,6 +14,10 @@ Logged-in providers join the session model picker with their live model catalogs
14
14
 
15
15
  ![Model picker with subscription models](https://raw.githubusercontent.com/V1ki/dsh-plugin-subscriptions/main/docs/images/model-picker.png)
16
16
 
17
+ Models that advertise reasoning levels get an **Effort** selector in the same menu — Codex models, and Grok 4.6 / 4.5 (levels and defaults come from each provider's live catalog, not a hardcoded list):
18
+
19
+ ![Reasoning effort selector](https://raw.githubusercontent.com/V1ki/dsh-plugin-subscriptions/main/docs/images/model-effort.png)
20
+
17
21
  The `image_generate` tool renders its result inline in the conversation:
18
22
 
19
23
  ![image_generate renders the image inline](https://raw.githubusercontent.com/V1ki/dsh-plugin-subscriptions/main/docs/images/image-generate-inline.png)
@@ -24,7 +28,7 @@ The `image_generate` tool renders its result inline in the conversation:
24
28
  |----------|-------------------|--------|
25
29
  | `codex` | ChatGPT Plus/Pro | live catalog from `chatgpt.com/backend-api/codex/models` |
26
30
  | `claude` | Claude Pro/Max | claude-opus-4-5, claude-sonnet-4-5, claude-haiku-4-5 |
27
- | `grok` | X Premium (xAI) | live catalog from `api.x.ai/v1/models` (chat models only) |
31
+ | `grok` | X Premium (xAI) | live catalog from `api.x.ai/v1/models` (chat models only); reasoning efforts from the Grok CLI catalog (`cli-chat-proxy.grok.com/v1/models`) |
28
32
 
29
33
  Only logged-in providers appear in the session model picker; the lists above refresh on login/logout. Vision-capable models declare `['text', 'image']` input modalities, and image content is translated to each provider's wire format.
30
34
 
@@ -73,6 +77,18 @@ cp overlay.example.yml overlay.yml # then edit the name: to this checkout's ab
73
77
  dsh --profile headless --patch <checkout>/overlay.yml "your task"
74
78
  ```
75
79
 
80
+ ## Update
81
+
82
+ Installed from npm:
83
+
84
+ ```sh
85
+ dsh plugin --profile web update --latest dsh-plugin-subscriptions
86
+ ```
87
+
88
+ Installed from GitHub: re-run the same `add github:V1ki/dsh-plugin-subscriptions` command — it re-fetches the sources and rebuilds. A linked local checkout just needs `git pull && pnpm build` in the checkout.
89
+
90
+ Either way, restart `dsh web` afterwards so the new version loads.
91
+
76
92
  ## Use
77
93
 
78
94
  1. `dsh web`, open the printed URL.
package/README.zh.md CHANGED
@@ -14,6 +14,10 @@
14
14
 
15
15
  ![模型选择器中的订阅模型](https://raw.githubusercontent.com/V1ki/dsh-plugin-subscriptions/main/docs/images/model-picker.png)
16
16
 
17
+ 声明了推理等级的模型会在同一菜单里多出**推理等级**选择 —— Codex 系列模型,以及 Grok 4.6 / 4.5(档位和默认值来自各 provider 的实时目录,不是硬编码列表):
18
+
19
+ ![推理等级选择器](https://raw.githubusercontent.com/V1ki/dsh-plugin-subscriptions/main/docs/images/model-effort.png)
20
+
17
21
  `image_generate` 工具生成的图片直接内联显示在对话里:
18
22
 
19
23
  ![image_generate 内联显示生成的图片](https://raw.githubusercontent.com/V1ki/dsh-plugin-subscriptions/main/docs/images/image-generate-inline.png)
@@ -24,7 +28,7 @@
24
28
  |----------|------------------|------|
25
29
  | `codex` | ChatGPT Plus/Pro | 从 `chatgpt.com/backend-api/codex/models` 实时获取 |
26
30
  | `claude` | Claude Pro/Max | claude-opus-4-5、claude-sonnet-4-5、claude-haiku-4-5 |
27
- | `grok` | X Premium (xAI) | 从 `api.x.ai/v1/models` 实时获取(仅对话模型) |
31
+ | `grok` | X Premium (xAI) | 从 `api.x.ai/v1/models` 实时获取(仅对话模型);推理等级来自 Grok CLI 目录(`cli-chat-proxy.grok.com/v1/models`) |
28
32
 
29
33
  只有已登录的 provider 才会出现在会话模型选择器里;登录/退出后列表自动刷新。支持视觉的模型会声明 `['text', 'image']` 输入模态,图片内容会被翻译成各 provider 的 wire 格式。
30
34
 
@@ -73,6 +77,18 @@ cp overlay.example.yml overlay.yml # 然后把 name: 改成本检出的 lib/in
73
77
  dsh --profile headless --patch <检出目录>/overlay.yml "你的任务"
74
78
  ```
75
79
 
80
+ ## 更新
81
+
82
+ npm 安装的:
83
+
84
+ ```sh
85
+ dsh plugin --profile web update --latest dsh-plugin-subscriptions
86
+ ```
87
+
88
+ GitHub 安装的:重新执行一遍 `add github:V1ki/dsh-plugin-subscriptions` —— 会重新拉取源码并构建。link 的本地检出只需在检出目录里 `git pull && pnpm build`。
89
+
90
+ 无论哪种方式,更新后都要重启 `dsh web` 才会加载新版本。
91
+
76
92
  ## 使用
77
93
 
78
94
  1. `dsh web`,打开打印的 URL。
package/lib/index.js CHANGED
@@ -2439,6 +2439,58 @@ function grokModalities(id) {
2439
2439
  return /code|embed/i.test(id) ? ["text"] : ["text", "image"];
2440
2440
  }
2441
2441
  /**
2442
+ * The Grok Build CLI chat proxy's model catalog — the only grok endpoint that
2443
+ * advertises reasoning capability. The `api.x.ai/v1/models` and
2444
+ * `/v1/language-models` payloads carry pricing, context, and aliases only, so
2445
+ * effort metadata must come from here (the same source the official CLI's
2446
+ * picker uses).
2447
+ */
2448
+ const GROK_CLI_MODELS_URL = "https://cli-chat-proxy.grok.com/v1/models";
2449
+ /** Map one CLI catalog entry's reasoning fields, or undefined when unsupported. */
2450
+ function grokCliReasoning(entry) {
2451
+ if (entry.supports_reasoning_effort !== true) return void 0;
2452
+ const efforts = (entry.reasoning_efforts ?? []).filter((level) => typeof level.value === "string" && level.value.length > 0).map((level) => ({
2453
+ id: ReasoningEffortId(level.value),
2454
+ name: typeof level.label === "string" && level.label.length > 0 ? level.label : level.value,
2455
+ ...typeof level.description === "string" && level.description.length > 0 ? { description: level.description } : {}
2456
+ }));
2457
+ if (efforts.length === 0) return void 0;
2458
+ const defaultEffort = typeof entry.reasoning_effort === "string" && efforts.some((effort) => effort.id === ReasoningEffortId(entry.reasoning_effort)) ? ReasoningEffortId(entry.reasoning_effort) : void 0;
2459
+ return {
2460
+ efforts,
2461
+ ...defaultEffort === void 0 ? {} : { defaultEffort }
2462
+ };
2463
+ }
2464
+ /**
2465
+ * Fetch the CLI catalog and index its per-model metadata by model id.
2466
+ * @param session - the stored session (used as-is; never refreshed here).
2467
+ * @param fetchFn - fetch implementation (injectable for tests).
2468
+ * @returns model id → contributed metadata.
2469
+ */
2470
+ async function fetchGrokCliCatalog(session, fetchFn = fetch) {
2471
+ const response = await fetchFn(GROK_CLI_MODELS_URL, { headers: {
2472
+ "authorization": `Bearer ${session.accessToken}`,
2473
+ "x-xai-token-auth": "xai-grok-cli",
2474
+ "accept": "application/json",
2475
+ ...attributionHeaders()
2476
+ } });
2477
+ if (!response.ok) throw await oauthEndpointError(response, "grok CLI catalog");
2478
+ const payload = await response.json();
2479
+ if (!Array.isArray(payload.data)) throw new Error("grok CLI catalog returned no data array");
2480
+ const catalog = /* @__PURE__ */ new Map();
2481
+ for (const entry of payload.data) {
2482
+ if (typeof entry.id !== "string" || entry.id.length === 0) continue;
2483
+ const reasoning = grokCliReasoning(entry);
2484
+ catalog.set(entry.id, {
2485
+ ...typeof entry.name === "string" && entry.name.length > 0 ? { name: entry.name } : {},
2486
+ ...typeof entry.description === "string" && entry.description.length > 0 ? { description: entry.description } : {},
2487
+ ...typeof entry.context_window === "number" && entry.context_window > 0 ? { contextWindow: entry.context_window } : {},
2488
+ ...reasoning === void 0 ? {} : { reasoning }
2489
+ });
2490
+ }
2491
+ return catalog;
2492
+ }
2493
+ /**
2442
2494
  * The /v1/models list also serves generation models that cannot chat
2443
2495
  * (grok-imagine-image*, grok-imagine-video*) and embedding models; the picker
2444
2496
  * must not offer them. Heuristic over the id substring, verified against the
@@ -2448,17 +2500,24 @@ function isChatModel(id) {
2448
2500
  return !/imagine|image-|video|embed/i.test(id);
2449
2501
  }
2450
2502
  /**
2451
- * Fetch the live grok model list.
2503
+ * Fetch the live grok model list, enriched with the CLI catalog's per-model
2504
+ * metadata (display name, context window, reasoning efforts). The api.x.ai
2505
+ * list stays authoritative for which models exist; the CLI catalog is
2506
+ * enrichment only, so its failure degrades to a plain list instead of taking
2507
+ * discovery down — models it does not cover simply expose no efforts.
2452
2508
  * @param session - the stored session (used as-is; never refreshed here).
2453
2509
  * @param fetchFn - fetch implementation (injectable for tests).
2454
- * @returns discovered chat models in endpoint order (id doubles as the name).
2510
+ * @param onWarn - warning sink for a failed CLI catalog fetch.
2511
+ * @returns discovered chat models in endpoint order.
2455
2512
  */
2456
- async function fetchGrokModels(session, fetchFn = fetch) {
2457
- const response = await fetchFn(GROK_MODELS_URL, { headers: {
2513
+ async function fetchGrokModels(session, fetchFn = fetch, onWarn) {
2514
+ const [response, cliCatalog] = await Promise.all([fetchFn(GROK_MODELS_URL, { headers: {
2458
2515
  "authorization": `Bearer ${session.accessToken}`,
2459
2516
  "accept": "application/json",
2460
2517
  ...attributionHeaders()
2461
- } });
2518
+ } }), fetchGrokCliCatalog(session, fetchFn).catch((error) => {
2519
+ onWarn?.(`grok CLI catalog fetch failed; reasoning efforts are unavailable (${errorChain(error)})`);
2520
+ })]);
2462
2521
  if (!response.ok) throw await oauthEndpointError(response, "grok models");
2463
2522
  const payload = await response.json();
2464
2523
  if (!Array.isArray(payload.data)) throw new Error("grok models endpoint returned no data array");
@@ -2470,7 +2529,8 @@ async function fetchGrokModels(session, fetchFn = fetch) {
2470
2529
  seen.add(entry.id);
2471
2530
  discovered.push({
2472
2531
  id: entry.id,
2473
- name: entry.id
2532
+ name: entry.id,
2533
+ ...cliCatalog?.get(entry.id)
2474
2534
  });
2475
2535
  }
2476
2536
  if (discovered.length === 0) throw new Error("grok models endpoint returned an empty catalog");
@@ -2501,10 +2561,11 @@ var GrokAdapter = class extends LlmAdapter {
2501
2561
  if (await this.options.tokens.peek() === void 0) return [];
2502
2562
  if (!this.options.discovery) return this.staticModels(provider);
2503
2563
  try {
2504
- return (await this.catalog.get(async () => fetchGrokModels(await this.options.tokens.session(), this.options.fetchFn))).map((model) => ({
2564
+ return (await this.catalog.get(async () => fetchGrokModels(await this.options.tokens.session(), this.options.fetchFn, this.options.onWarn))).map((model) => ({
2505
2565
  provider,
2506
2566
  id: model.id,
2507
2567
  name: model.name,
2568
+ ...model.description === void 0 ? {} : { description: model.description },
2508
2569
  inputModalities: grokModalities(model.id)
2509
2570
  }));
2510
2571
  } catch (error) {
@@ -2521,9 +2582,11 @@ var GrokAdapter = class extends LlmAdapter {
2521
2582
  provider,
2522
2583
  id: model,
2523
2584
  name: discovered?.name ?? configured?.name ?? model,
2585
+ ...discovered?.description === void 0 ? {} : { description: discovered.description },
2524
2586
  inputModalities: configured?.inputModalities ?? grokModalities(model),
2525
- context: { contextWindow: configured?.contextWindow ?? GROK_CONTEXT_WINDOW },
2526
- defaultMaxTokens: configured?.maxTokens ?? GROK_DEFAULT_MAX_TOKENS
2587
+ context: { contextWindow: discovered?.contextWindow ?? configured?.contextWindow ?? GROK_CONTEXT_WINDOW },
2588
+ defaultMaxTokens: configured?.maxTokens ?? GROK_DEFAULT_MAX_TOKENS,
2589
+ ...discovered?.reasoning === void 0 ? {} : { reasoning: discovered.reasoning }
2527
2590
  });
2528
2591
  }
2529
2592
  async *stream(options) {
@@ -2556,6 +2619,7 @@ var GrokAdapter = class extends LlmAdapter {
2556
2619
  tool_choice: "auto",
2557
2620
  parallel_tool_calls: true,
2558
2621
  ...options.maxTokens !== void 0 ? { max_output_tokens: options.maxTokens } : {},
2622
+ ...options.reasoningEffort !== void 0 ? { reasoning: { effort: String(options.reasoningEffort) } } : {},
2559
2623
  store: false,
2560
2624
  stream: true
2561
2625
  };
@@ -80,12 +80,34 @@ export declare const GROK_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/bill
80
80
  export declare function fetchGrokUsage(session: GrokSession, fetchFn?: FetchFn, signal?: AbortSignal): Promise<ProviderUsage>;
81
81
  export declare const GROK_MODELS_URL = "https://api.x.ai/v1/models";
82
82
  /**
83
- * Fetch the live grok model list.
83
+ * The Grok Build CLI chat proxy's model catalog — the only grok endpoint that
84
+ * advertises reasoning capability. The `api.x.ai/v1/models` and
85
+ * `/v1/language-models` payloads carry pricing, context, and aliases only, so
86
+ * effort metadata must come from here (the same source the official CLI's
87
+ * picker uses).
88
+ */
89
+ export declare const GROK_CLI_MODELS_URL = "https://cli-chat-proxy.grok.com/v1/models";
90
+ /** Per-model metadata the CLI catalog contributes to a discovered model. */
91
+ type GrokCliModelMeta = Partial<Pick<DiscoveredModel, 'name' | 'description' | 'contextWindow' | 'reasoning'>>;
92
+ /**
93
+ * Fetch the CLI catalog and index its per-model metadata by model id.
94
+ * @param session - the stored session (used as-is; never refreshed here).
95
+ * @param fetchFn - fetch implementation (injectable for tests).
96
+ * @returns model id → contributed metadata.
97
+ */
98
+ export declare function fetchGrokCliCatalog(session: GrokSession, fetchFn?: FetchFn): Promise<Map<string, GrokCliModelMeta>>;
99
+ /**
100
+ * Fetch the live grok model list, enriched with the CLI catalog's per-model
101
+ * metadata (display name, context window, reasoning efforts). The api.x.ai
102
+ * list stays authoritative for which models exist; the CLI catalog is
103
+ * enrichment only, so its failure degrades to a plain list instead of taking
104
+ * discovery down — models it does not cover simply expose no efforts.
84
105
  * @param session - the stored session (used as-is; never refreshed here).
85
106
  * @param fetchFn - fetch implementation (injectable for tests).
86
- * @returns discovered chat models in endpoint order (id doubles as the name).
107
+ * @param onWarn - warning sink for a failed CLI catalog fetch.
108
+ * @returns discovered chat models in endpoint order.
87
109
  */
88
- export declare function fetchGrokModels(session: GrokSession, fetchFn?: FetchFn): Promise<DiscoveredModel[]>;
110
+ export declare function fetchGrokModels(session: GrokSession, fetchFn?: FetchFn, onWarn?: (message: string) => void): Promise<DiscoveredModel[]>;
89
111
  /** Constructor dependencies for {@link GrokAdapter}. */
90
112
  export interface GrokAdapterOptions {
91
113
  models: readonly ModelEntry[];
@@ -112,3 +134,4 @@ export declare class GrokAdapter extends LlmAdapter {
112
134
  stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
113
135
  private request;
114
136
  }
137
+ export {};
@@ -3,7 +3,7 @@
3
3
  * auth.x.ai with the Grok CLI client id, and streaming against the xAI
4
4
  * Responses-style endpoint.
5
5
  */
6
- import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError } from '@deepseek-ai/dsh-llm';
6
+ import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
7
7
  import { decodeJwtPayload } from '../auth/jwt.js';
8
8
  import { resolveImages } from '../translate/resolved.js';
9
9
  import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
@@ -276,28 +276,110 @@ function grokModalities(id) {
276
276
  return /code|embed/i.test(id) ? ['text'] : ['text', 'image'];
277
277
  }
278
278
  /**
279
- * The /v1/models list also serves generation models that cannot chat
280
- * (grok-imagine-image*, grok-imagine-video*) and embedding models; the picker
281
- * must not offer them. Heuristic over the id substring, verified against the
282
- * live catalog (grok-build-0.1 and the grok-4 family pass).
279
+ * The Grok Build CLI chat proxy's model catalog the only grok endpoint that
280
+ * advertises reasoning capability. The `api.x.ai/v1/models` and
281
+ * `/v1/language-models` payloads carry pricing, context, and aliases only, so
282
+ * effort metadata must come from here (the same source the official CLI's
283
+ * picker uses).
283
284
  */
284
- function isChatModel(id) {
285
- return !/imagine|image-|video|embed/i.test(id);
285
+ export const GROK_CLI_MODELS_URL = 'https://cli-chat-proxy.grok.com/v1/models';
286
+ /** Map one CLI catalog entry's reasoning fields, or undefined when unsupported. */
287
+ function grokCliReasoning(entry) {
288
+ if (entry.supports_reasoning_effort !== true)
289
+ return undefined;
290
+ const efforts = (entry.reasoning_efforts ?? [])
291
+ .filter(level => typeof level.value === 'string' && level.value.length > 0)
292
+ .map(level => ({
293
+ id: ReasoningEffortId(level.value),
294
+ name: typeof level.label === 'string' && level.label.length > 0 ? level.label : level.value,
295
+ ...typeof level.description === 'string' && level.description.length > 0
296
+ ? { description: level.description }
297
+ : {},
298
+ }));
299
+ if (efforts.length === 0)
300
+ return undefined;
301
+ // The per-entry `default` flags are unreliable (the live catalog marks
302
+ // several levels default at once), so the top-level `reasoning_effort`
303
+ // field is the trusted default.
304
+ const defaultEffort = typeof entry.reasoning_effort === 'string'
305
+ && efforts.some(effort => effort.id === ReasoningEffortId(entry.reasoning_effort))
306
+ ? ReasoningEffortId(entry.reasoning_effort)
307
+ : undefined;
308
+ return { efforts, ...defaultEffort === undefined ? {} : { defaultEffort } };
286
309
  }
287
310
  /**
288
- * Fetch the live grok model list.
311
+ * Fetch the CLI catalog and index its per-model metadata by model id.
289
312
  * @param session - the stored session (used as-is; never refreshed here).
290
313
  * @param fetchFn - fetch implementation (injectable for tests).
291
- * @returns discovered chat models in endpoint order (id doubles as the name).
314
+ * @returns model id contributed metadata.
292
315
  */
293
- export async function fetchGrokModels(session, fetchFn = fetch) {
294
- const response = await fetchFn(GROK_MODELS_URL, {
316
+ export async function fetchGrokCliCatalog(session, fetchFn = fetch) {
317
+ const response = await fetchFn(GROK_CLI_MODELS_URL, {
295
318
  headers: {
296
319
  'authorization': `Bearer ${session.accessToken}`,
320
+ // The proxy only honors bearer tokens presented as the Grok CLI.
321
+ 'x-xai-token-auth': 'xai-grok-cli',
297
322
  'accept': 'application/json',
298
323
  ...attributionHeaders(),
299
324
  },
300
325
  });
326
+ if (!response.ok)
327
+ throw await oauthEndpointError(response, 'grok CLI catalog');
328
+ const payload = await response.json();
329
+ if (!Array.isArray(payload.data))
330
+ throw new Error('grok CLI catalog returned no data array');
331
+ const catalog = new Map();
332
+ for (const entry of payload.data) {
333
+ if (typeof entry.id !== 'string' || entry.id.length === 0)
334
+ continue;
335
+ const reasoning = grokCliReasoning(entry);
336
+ catalog.set(entry.id, {
337
+ ...typeof entry.name === 'string' && entry.name.length > 0 ? { name: entry.name } : {},
338
+ ...typeof entry.description === 'string' && entry.description.length > 0
339
+ ? { description: entry.description }
340
+ : {},
341
+ ...typeof entry.context_window === 'number' && entry.context_window > 0
342
+ ? { contextWindow: entry.context_window }
343
+ : {},
344
+ ...reasoning === undefined ? {} : { reasoning },
345
+ });
346
+ }
347
+ return catalog;
348
+ }
349
+ /**
350
+ * The /v1/models list also serves generation models that cannot chat
351
+ * (grok-imagine-image*, grok-imagine-video*) and embedding models; the picker
352
+ * must not offer them. Heuristic over the id substring, verified against the
353
+ * live catalog (grok-build-0.1 and the grok-4 family pass).
354
+ */
355
+ function isChatModel(id) {
356
+ return !/imagine|image-|video|embed/i.test(id);
357
+ }
358
+ /**
359
+ * Fetch the live grok model list, enriched with the CLI catalog's per-model
360
+ * metadata (display name, context window, reasoning efforts). The api.x.ai
361
+ * list stays authoritative for which models exist; the CLI catalog is
362
+ * enrichment only, so its failure degrades to a plain list instead of taking
363
+ * discovery down — models it does not cover simply expose no efforts.
364
+ * @param session - the stored session (used as-is; never refreshed here).
365
+ * @param fetchFn - fetch implementation (injectable for tests).
366
+ * @param onWarn - warning sink for a failed CLI catalog fetch.
367
+ * @returns discovered chat models in endpoint order.
368
+ */
369
+ export async function fetchGrokModels(session, fetchFn = fetch, onWarn) {
370
+ const [response, cliCatalog] = await Promise.all([
371
+ fetchFn(GROK_MODELS_URL, {
372
+ headers: {
373
+ 'authorization': `Bearer ${session.accessToken}`,
374
+ 'accept': 'application/json',
375
+ ...attributionHeaders(),
376
+ },
377
+ }),
378
+ fetchGrokCliCatalog(session, fetchFn).catch((error) => {
379
+ onWarn?.(`grok CLI catalog fetch failed; reasoning efforts are unavailable (${errorChain(error)})`);
380
+ return undefined;
381
+ }),
382
+ ]);
301
383
  if (!response.ok)
302
384
  throw await oauthEndpointError(response, 'grok models');
303
385
  const payload = await response.json();
@@ -311,7 +393,7 @@ export async function fetchGrokModels(session, fetchFn = fetch) {
311
393
  if (!isChatModel(entry.id))
312
394
  continue;
313
395
  seen.add(entry.id);
314
- discovered.push({ id: entry.id, name: entry.id });
396
+ discovered.push({ id: entry.id, name: entry.id, ...cliCatalog?.get(entry.id) });
315
397
  }
316
398
  // An empty catalog from a 200 response is treated as a discovery failure so
317
399
  // the adapter falls back to the static catalog instead of vanishing from
@@ -350,11 +432,12 @@ export class GrokAdapter extends LlmAdapter {
350
432
  // The fetcher runs only on a cache miss, and resolves the session
351
433
  // through the refresh-aware path so an expired access token renews here
352
434
  // instead of failing discovery into the static fallback.
353
- const discovered = await this.catalog.get(async () => fetchGrokModels(await this.options.tokens.session(), this.options.fetchFn));
435
+ const discovered = await this.catalog.get(async () => fetchGrokModels(await this.options.tokens.session(), this.options.fetchFn, this.options.onWarn));
354
436
  return discovered.map(model => ({
355
437
  provider,
356
438
  id: model.id,
357
439
  name: model.name,
440
+ ...model.description === undefined ? {} : { description: model.description },
358
441
  inputModalities: grokModalities(model.id),
359
442
  }));
360
443
  }
@@ -379,10 +462,14 @@ export class GrokAdapter extends LlmAdapter {
379
462
  provider,
380
463
  id: model,
381
464
  name: discovered?.name ?? configured?.name ?? model,
465
+ ...discovered?.description === undefined ? {} : { description: discovered.description },
382
466
  inputModalities: configured?.inputModalities ?? grokModalities(model),
383
- context: { contextWindow: configured?.contextWindow ?? GROK_CONTEXT_WINDOW },
467
+ context: { contextWindow: discovered?.contextWindow ?? configured?.contextWindow ?? GROK_CONTEXT_WINDOW },
384
468
  defaultMaxTokens: configured?.maxTokens ?? GROK_DEFAULT_MAX_TOKENS,
385
- // No reasoning metadata: effort selection is not exposed for grok.
469
+ // Efforts come from the discovered CLI catalog; models it does not
470
+ // cover expose none, so the harness rejects explicit efforts before
471
+ // provider I/O instead of the API 400ing.
472
+ ...discovered?.reasoning === undefined ? {} : { reasoning: discovered.reasoning },
386
473
  });
387
474
  }
388
475
  async *stream(options) {
@@ -422,6 +509,11 @@ export class GrokAdapter extends LlmAdapter {
422
509
  tool_choice: 'auto',
423
510
  parallel_tool_calls: true,
424
511
  ...options.maxTokens !== undefined ? { max_output_tokens: options.maxTokens } : {},
512
+ // The harness only passes an effort the resolved model advertised (the
513
+ // CLI catalog's), so this never reaches a model that rejects it.
514
+ ...options.reasoningEffort !== undefined
515
+ ? { reasoning: { effort: String(options.reasoningEffort) } }
516
+ : {},
425
517
  store: false,
426
518
  stream: true,
427
519
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-subscriptions",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Use ChatGPT (Codex), Claude, and Grok (X Premium) subscriptions as DeepSeek Harness LLM providers, with OAuth login from the web Settings page",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,12 +48,6 @@
48
48
  ]
49
49
  }
50
50
  },
51
- "scripts": {
52
- "build": "tsc && tsdown",
53
- "test": "tsc -p tsconfig.test.json && node --test lib-test/test/",
54
- "prepare": "tsdown -c tsdown.prepare.config.ts",
55
- "prepublishOnly": "pnpm build && pnpm test"
56
- },
57
51
  "peerDependencies": {
58
52
  "@deepseek-ai/cordis": "^4.0.1",
59
53
  "@deepseek-ai/dsh-attachment": "^0.1.0-rc.5",
@@ -81,5 +75,9 @@
81
75
  "react": "^18.2.0",
82
76
  "tsdown": "^0.15.0",
83
77
  "typescript": "^5.8.0"
78
+ },
79
+ "scripts": {
80
+ "build": "tsc && tsdown",
81
+ "test": "tsc -p tsconfig.test.json && node --test lib-test/test/"
84
82
  }
85
- }
83
+ }