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 +17 -1
- package/README.zh.md +17 -1
- package/lib/index.js +73 -9
- package/lib/providers/grok.d.ts +26 -3
- package/lib/providers/grok.js +107 -15
- package/package.json +6 -8
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
|

|
|
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
|
+

|
|
20
|
+
|
|
17
21
|
The `image_generate` tool renders its result inline in the conversation:
|
|
18
22
|
|
|
19
23
|

|
|
@@ -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
|

|
|
16
16
|
|
|
17
|
+
声明了推理等级的模型会在同一菜单里多出**推理等级**选择 —— Codex 系列模型,以及 Grok 4.6 / 4.5(档位和默认值来自各 provider 的实时目录,不是硬编码列表):
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
17
21
|
`image_generate` 工具生成的图片直接内联显示在对话里:
|
|
18
22
|
|
|
19
23
|

|
|
@@ -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
|
-
* @
|
|
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
|
};
|
package/lib/providers/grok.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
* @
|
|
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 {};
|
package/lib/providers/grok.js
CHANGED
|
@@ -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
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
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
|
-
|
|
285
|
-
|
|
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
|
|
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
|
|
314
|
+
* @returns model id → contributed metadata.
|
|
292
315
|
*/
|
|
293
|
-
export async function
|
|
294
|
-
const response = await fetchFn(
|
|
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
|
-
//
|
|
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.
|
|
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
|
+
}
|