dsh-scnet-connect 1.0.1 → 1.1.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 +26 -0
- package/lib/index.js +27 -1
- package/lib/scnet-fetch.js +153 -0
- package/lib/scnet-models.js +26 -24
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
- **零客户端**:不装 SCNet 桌面客户端,用手机短信登录即可使用
|
|
10
10
|
- **零配置**:装了客户端的话,自动读凭据、自动发现免费模型
|
|
11
11
|
- **设置页管理**:账号、登录、模型开关都在 DSH 设置里
|
|
12
|
+
- **1M 上下文**:`SCNet-Max` 按平台真实能力声明 `contextWindow: 1048576` / `maxTokens: 131072`(实测 104.5 万 token 输入通过、1.06M 被拒,见 [docs/scnet-api.md](docs/scnet-api.md))
|
|
13
|
+
- **抖动自愈**:平台偶发的 `503 + 10008`(路由切换)会被归一化成可重试错误,自动退避重试而不是整轮失败
|
|
12
14
|
|
|
13
15
|

|
|
14
16
|
|
|
@@ -194,9 +196,32 @@ dsh plugin --profile desktop add link:.
|
|
|
194
196
|
| `inputImages` | `true` | 是否声明接受图片 |
|
|
195
197
|
| `reasoning` | `true` | 是否声明支持思考等级 |
|
|
196
198
|
| `verifyWithGateway` | `true` | 刷新时用 `GET /models` 校验 |
|
|
199
|
+
| `retryOnTransientErrors` | `true` | 把 SCNet「5xx 却标成 `invalid_request_error`」的瞬时抖动改写成可重试的服务端错误(见下) |
|
|
197
200
|
|
|
198
201
|
</details>
|
|
199
202
|
|
|
203
|
+
### 关于 `retryOnTransientErrors`
|
|
204
|
+
|
|
205
|
+
SCNet 在推理实例切换/回收时会返回这种响应:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
HTTP 503
|
|
209
|
+
{"error":{"message":"The model's routing or availability changed while processing the request. Please try again.","type":"invalid_request_error","code":"10008"}}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
状态码是 5xx,但响应体 `type` 写的是 `invalid_request_error`。DSH 的错误归类按正则顺序扫描文本,`invalid.?request` 排在 `5xx → SERVER` 前面,于是把它判成 **`INVALID_REQUEST`(不可重试)**,一次退避都不做,本轮直接失败。
|
|
213
|
+
|
|
214
|
+
插件默认把这层归一化打开:只针对「状态码 ≥ 500 且 `type` 是 `invalid_request_error`」的响应,把 `type` 改写为 `api_error`(状态码与 message 原样保留),让归类落到 `SERVER`,交给 DSH 的 5 次指数退避重试。
|
|
215
|
+
|
|
216
|
+
刻意不碰的:真正的 `400`(如参数错误 `10013`)、`401/403/402/429`、以及 2xx 流式响应。
|
|
217
|
+
|
|
218
|
+
实测(本地假网关,前 2 次返回上述 503):
|
|
219
|
+
|
|
220
|
+
| 配置 | chat 请求数 | 结果 |
|
|
221
|
+
| --- | --- | --- |
|
|
222
|
+
| `retryOnTransientErrors: false` | 2(无重试) | 失败,`code: INVALID_REQUEST` |
|
|
223
|
+
| `retryOnTransientErrors: true`(默认) | 4(2 次 503 后成功) | 正常完成 |
|
|
224
|
+
|
|
200
225
|
---
|
|
201
226
|
|
|
202
227
|
## 故障排查
|
|
@@ -210,6 +235,7 @@ dsh plugin --profile desktop add link:.
|
|
|
210
235
|
| 提示「平台会话已过期」 | 在设置页重新登录一次 |
|
|
211
236
|
| 短信收不到 | 确认手机号正确、图形验证码没填错;平台有 60 秒与每小时次数限制 |
|
|
212
237
|
| 上游 429 | 插件不屏蔽限流,降低并发或稍等重试 |
|
|
238
|
+
| 报 `503` + `code: 10008` / `10009` | 平台侧路由抖动。默认会自动重试;若已被手动关掉 `retryOnTransientErrors`,打开它或稍后重发 |
|
|
213
239
|
|
|
214
240
|
---
|
|
215
241
|
|
package/lib/index.js
CHANGED
|
@@ -39,6 +39,7 @@ import path from 'node:path';
|
|
|
39
39
|
|
|
40
40
|
import { maskKey, readClientAccount, resolveApiKey } from './scnet-credential.js';
|
|
41
41
|
import { createModelCatalog } from './scnet-catalog.js';
|
|
42
|
+
import { createRetryFriendlyFetch, withRetryFriendlyFetch } from './scnet-fetch.js';
|
|
42
43
|
import { createLoginManager } from './scnet-login.js';
|
|
43
44
|
import { DEFAULT_BASE_URL, DEFAULT_MODEL_ID, buildModel, modelLabel } from './scnet-models.js';
|
|
44
45
|
|
|
@@ -112,6 +113,12 @@ export const Config = z.object({
|
|
|
112
113
|
.boolean()
|
|
113
114
|
.default(true)
|
|
114
115
|
.description('刷新模型时用网关 GET /models 校验可用性(关闭后只读本地客户端目录)'),
|
|
116
|
+
retryOnTransientErrors: z
|
|
117
|
+
.boolean()
|
|
118
|
+
.default(true)
|
|
119
|
+
.description(
|
|
120
|
+
'把 SCNet「5xx 但响应体标成 invalid_request」的瞬时抖动改写成可重试的服务端错误,交给 DSH 退避重试(关闭后这类 503 会直接失败)',
|
|
121
|
+
),
|
|
115
122
|
freeOnly: z
|
|
116
123
|
.boolean()
|
|
117
124
|
.default(true)
|
|
@@ -197,6 +204,7 @@ export async function apply(ctx, config) {
|
|
|
197
204
|
inputImages: config?.inputImages !== false,
|
|
198
205
|
reasoning: config?.reasoning !== false,
|
|
199
206
|
verifyWithGateway: config?.verifyWithGateway !== false,
|
|
207
|
+
retryOnTransientErrors: config?.retryOnTransientErrors !== false,
|
|
200
208
|
freeOnly: config?.freeOnly !== false,
|
|
201
209
|
};
|
|
202
210
|
|
|
@@ -408,6 +416,22 @@ export async function apply(ctx, config) {
|
|
|
408
416
|
);
|
|
409
417
|
};
|
|
410
418
|
|
|
419
|
+
/**
|
|
420
|
+
* 上游「5xx 却自称 invalid_request」的归一化 fetch(见 `lib/scnet-fetch.js`)。
|
|
421
|
+
*
|
|
422
|
+
* SCNet 在推理实例切换时返回 503 + `type:"invalid_request_error"` +
|
|
423
|
+
* `code:"10008"`,DSH 的归类器会先命中 `invalid.?request` 把它判成
|
|
424
|
+
* `INVALID_REQUEST`(不可重试),于是一次本可自愈的抖动直接让本轮失败。
|
|
425
|
+
* 这里把它改写成 `api_error`,落到 `SERVER` 分支,交给 DSH 的退避重试。
|
|
426
|
+
*/
|
|
427
|
+
const retryFriendlyFetch = createRetryFriendlyFetch({
|
|
428
|
+
onNormalize: ({ status, code, message }) => {
|
|
429
|
+
logger.warn(
|
|
430
|
+
`上游返回 ${status}(code=${code ?? '-'}),已按可重试的服务端错误处理:${message ?? ''}`.trim(),
|
|
431
|
+
);
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
|
|
411
435
|
const buildProfile = () => {
|
|
412
436
|
const models = buildModels();
|
|
413
437
|
const provider = {
|
|
@@ -445,7 +469,9 @@ export async function apply(ctx, config) {
|
|
|
445
469
|
},
|
|
446
470
|
},
|
|
447
471
|
models,
|
|
448
|
-
api:
|
|
472
|
+
api: options.retryOnTransientErrors
|
|
473
|
+
? withRetryFriendlyFetch(openAICompletionsApi(), retryFriendlyFetch)
|
|
474
|
+
: openAICompletionsApi(),
|
|
449
475
|
}),
|
|
450
476
|
// 模型目录可能随客户端目录更新,每次读取都重建。
|
|
451
477
|
getModels: () => buildModels(),
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上游错误归一化
|
|
3
|
+
* ------------------------------------------------------------------
|
|
4
|
+
* 目的:让 SCNet 的「瞬时路由抖动」能被 DSH 的重试策略接住。
|
|
5
|
+
*
|
|
6
|
+
* 问题形状
|
|
7
|
+
* --------
|
|
8
|
+
* SCNet 网关在推理实例被回收/切换时会返回:
|
|
9
|
+
*
|
|
10
|
+
* HTTP 503
|
|
11
|
+
* {"error":{"message":"The model's routing or availability changed while
|
|
12
|
+
* processing the request. Please try again.","type":"invalid_request_error",
|
|
13
|
+
* "code":"10008"}}
|
|
14
|
+
*
|
|
15
|
+
* 即:**状态码是 5xx,但响应体里的 `type` 写的是 `invalid_request_error`**。
|
|
16
|
+
*
|
|
17
|
+
* DSH 侧的错误归类(`dsh-llm-pi-ai` 的 `classifyPiAiError`)按正则顺序扫描
|
|
18
|
+
* 错误文本,`invalid.?request` 那条规则排在 `5xx → SERVER` **前面**:
|
|
19
|
+
*
|
|
20
|
+
* /\b400\b|invalid.?request/i → INVALID_REQUEST ← 先命中
|
|
21
|
+
* /\b5\d\d\b/ → SERVER ← 永远轮不到
|
|
22
|
+
*
|
|
23
|
+
* 而 `INVALID_REQUEST` 不在默认可重试集合
|
|
24
|
+
* (`[EMPTY_RESPONSE, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT]`)里,
|
|
25
|
+
* 于是 DSH 把一次本可自愈的抖动判成本轮失败,一次都不重试。
|
|
26
|
+
*
|
|
27
|
+
* 做法
|
|
28
|
+
* ----
|
|
29
|
+
* 在 provider 的 fetch 上包一层:**只**针对「状态码 >= 500 且 body.type 是
|
|
30
|
+
* `invalid_request_error`」的响应,把 `type` 改写成 `api_error`(保持状态码与
|
|
31
|
+
* message 不变),让归类落到 `SERVER`,交给 DSH 的指数退避重试。
|
|
32
|
+
*
|
|
33
|
+
* 刻意**不**碰的:
|
|
34
|
+
* - 真正的 400(如 `10013` 参数非法)——那确实不该重试;
|
|
35
|
+
* - 401/403/402/429 —— 各自有明确语义,改写会误导上层;
|
|
36
|
+
* - 2xx 响应 —— 原样透传,连 body 都不读,不影响流式。
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** 会被改写的上游错误码(`code` 字段是字符串)。 */
|
|
40
|
+
export const RETRYABLE_UPSTREAM_CODES = Object.freeze(['10008', '10009']);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 判断一个上游错误是否应当被归一化成可重试的 `SERVER`。
|
|
44
|
+
*
|
|
45
|
+
* @param {number} status HTTP 状态码
|
|
46
|
+
* @param {any} body 已解析的响应体(可能不是 JSON)
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
export function shouldNormalizeUpstreamError(status, body) {
|
|
50
|
+
if (!(status >= 500)) return false;
|
|
51
|
+
const error = body?.error;
|
|
52
|
+
if (!error || typeof error !== 'object') return false;
|
|
53
|
+
// 只处理「5xx 却自称 invalid_request」这种自相矛盾的形状。
|
|
54
|
+
if (error.type !== 'invalid_request_error') return false;
|
|
55
|
+
// 有 code 时只认已知的抖动码,避免把未知的 5xx 也一并改写。
|
|
56
|
+
if (typeof error.code === 'string' && error.code.length > 0) {
|
|
57
|
+
return RETRYABLE_UPSTREAM_CODES.includes(error.code);
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 把错误体改写成 `api_error`(其余字段原样保留)。
|
|
64
|
+
*
|
|
65
|
+
* 保留原始 `code` 与 `message`,只是把 `type` 换成 DSH 归类器认得的服务端错误,
|
|
66
|
+
* 这样错误信息在 UI 上仍然可读、可搜。
|
|
67
|
+
*
|
|
68
|
+
* @param {any} body
|
|
69
|
+
* @returns {any}
|
|
70
|
+
*/
|
|
71
|
+
export function normalizeUpstreamErrorBody(body) {
|
|
72
|
+
return {
|
|
73
|
+
...body,
|
|
74
|
+
error: { ...body.error, type: 'api_error' },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 构造一个「重试友好」的 fetch。
|
|
80
|
+
*
|
|
81
|
+
* 只在非 2xx 且命中 {@link shouldNormalizeUpstreamError} 时介入;其它情况原样
|
|
82
|
+
* 返回原始 Response(不读取 body,流式响应不受影响)。
|
|
83
|
+
*
|
|
84
|
+
* @param {{ fetchImpl?: typeof fetch, onNormalize?: (info: { status: number, code?: string, message?: string }) => void }} [options]
|
|
85
|
+
* @returns {typeof fetch}
|
|
86
|
+
*/
|
|
87
|
+
export function createRetryFriendlyFetch(options = {}) {
|
|
88
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
89
|
+
if (typeof fetchImpl !== 'function') {
|
|
90
|
+
throw new Error('dsh-scnet-connect: no fetch implementation available');
|
|
91
|
+
}
|
|
92
|
+
return async function retryFriendlyFetch(input, init) {
|
|
93
|
+
const response = await fetchImpl(input, init);
|
|
94
|
+
if (response.ok) return response;
|
|
95
|
+
|
|
96
|
+
let raw;
|
|
97
|
+
try {
|
|
98
|
+
raw = await response.clone().text();
|
|
99
|
+
} catch {
|
|
100
|
+
return response;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let body;
|
|
104
|
+
try {
|
|
105
|
+
body = JSON.parse(raw);
|
|
106
|
+
} catch {
|
|
107
|
+
// 不是 JSON(例如 HTML 网关错误页):保持原样,交给上层按状态码处理。
|
|
108
|
+
return response;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!shouldNormalizeUpstreamError(response.status, body)) return response;
|
|
112
|
+
|
|
113
|
+
const rewritten = normalizeUpstreamErrorBody(body);
|
|
114
|
+
try {
|
|
115
|
+
options.onNormalize?.({
|
|
116
|
+
status: response.status,
|
|
117
|
+
code: typeof body?.error?.code === 'string' ? body.error.code : undefined,
|
|
118
|
+
message: typeof body?.error?.message === 'string' ? body.error.message : undefined,
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
// 日志回调失败不影响请求。
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const headers = new Headers(response.headers);
|
|
125
|
+
headers.delete('content-length');
|
|
126
|
+
headers.set('content-type', 'application/json; charset=utf-8');
|
|
127
|
+
return new Response(JSON.stringify(rewritten), {
|
|
128
|
+
status: response.status,
|
|
129
|
+
statusText: response.statusText,
|
|
130
|
+
headers,
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 用「重试友好」的 fetch 包一层 pi-ai 的 api 实现。
|
|
137
|
+
*
|
|
138
|
+
* pi-ai 的 `openai-completions` 会把 `options.fetch` 直接交给 OpenAI SDK
|
|
139
|
+
* (`createClient(..., fetch, ...)`),因此在这里注入即可覆盖整条请求链,
|
|
140
|
+
* 不需要改 DSH 或 pi-ai 本身。
|
|
141
|
+
*
|
|
142
|
+
* @param {{ stream: Function, streamSimple: Function }} api `openAICompletionsApi()` 的返回值
|
|
143
|
+
* @param {typeof fetch} fetchImpl
|
|
144
|
+
* @returns {{ stream: Function, streamSimple: Function }}
|
|
145
|
+
*/
|
|
146
|
+
export function withRetryFriendlyFetch(api, fetchImpl) {
|
|
147
|
+
const withFetch = (options) => ({ ...options, fetch: fetchImpl });
|
|
148
|
+
return {
|
|
149
|
+
...api,
|
|
150
|
+
stream: (model, context, options) => api.stream(model, context, withFetch(options)),
|
|
151
|
+
streamSimple: (model, context, options) => api.streamSimple(model, context, withFetch(options)),
|
|
152
|
+
};
|
|
153
|
+
}
|
package/lib/scnet-models.js
CHANGED
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
* 2. SCNet 客户端自己的模型目录 `~/.sclaw/agent/models.json`
|
|
11
11
|
* 3. 内置默认值
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* 上下文/输出上限以**平台真实能力**为准,而不是客户端主进程里那条旧特判
|
|
14
|
+
* (`if ("scnet-max" === id.toLowerCase()) contextWindow = min(ctx, 262144)`)。
|
|
15
|
+
*
|
|
16
|
+
* 2026-11-09 直连实测(`tools/repro/` 同款调用方式):
|
|
17
|
+
* - 输入 ~973k tokens(2,000,000 中文字符)→ HTTP 200,`prompt_tokens: 972977`;
|
|
18
|
+
* - 输入 ~1.07M tokens(2,200,000 中文字符)→ HTTP 400 `10013`;
|
|
19
|
+
* → 真实上下文就是 1,048,576(1M),客户端的 262,144 只是它自己保守取值。
|
|
20
|
+
* - `max_tokens: 131072` 流式请求被接受(客户端目录同样标 131072)。
|
|
21
|
+
* 若沿用 262144,DSH 会在 1/4 处就开始压缩上下文,白白浪费 3/4 的窗口。
|
|
16
22
|
*/
|
|
17
23
|
|
|
18
24
|
/** 客户端目录里标记「免费」的 priceLabel 值。 */
|
|
@@ -24,6 +30,17 @@ export const DEFAULT_MODEL_ID = 'SCNet-Max';
|
|
|
24
30
|
/** SCNet OpenAI 兼容网关根地址。 */
|
|
25
31
|
export const DEFAULT_BASE_URL = 'https://api.scnet.cn/api/llm/v1';
|
|
26
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 兜底上下文窗口(1M)。
|
|
35
|
+
*
|
|
36
|
+
* 客户端模型目录不可用时的默认值;与平台实测能力一致(见文件头)。
|
|
37
|
+
* 客户端目录存在时仍以目录里的 `contextWindow` 为准。
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_CONTEXT_WINDOW = 1_048_576;
|
|
40
|
+
|
|
41
|
+
/** 兜底最大输出(131072,平台实测接受该值)。 */
|
|
42
|
+
export const DEFAULT_MAX_TOKENS = 131_072;
|
|
43
|
+
|
|
27
44
|
/** node 侧无计费概念,全 0。 */
|
|
28
45
|
export const NO_COST = Object.freeze({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 });
|
|
29
46
|
|
|
@@ -46,22 +63,6 @@ export const THINKING_LEVEL_MAP = Object.freeze({
|
|
|
46
63
|
max: 'max',
|
|
47
64
|
});
|
|
48
65
|
|
|
49
|
-
/**
|
|
50
|
-
* 客户端对 SCNet-Max 的有效上限(与客户端主进程一致)。
|
|
51
|
-
* 其它模型只做「输出不超过上下文」的一致性约束。
|
|
52
|
-
*
|
|
53
|
-
* @param {string} modelId
|
|
54
|
-
* @param {number} contextWindow
|
|
55
|
-
* @param {number} maxTokens
|
|
56
|
-
*/
|
|
57
|
-
function clampForModel(modelId, contextWindow, maxTokens) {
|
|
58
|
-
if (modelId.trim().toLowerCase() !== DEFAULT_MODEL_ID.toLowerCase()) {
|
|
59
|
-
return { contextWindow, maxTokens: Math.min(maxTokens, contextWindow) };
|
|
60
|
-
}
|
|
61
|
-
const ctx = Math.min(contextWindow, 262144);
|
|
62
|
-
return { contextWindow: ctx, maxTokens: Math.min(maxTokens, ctx, 32768) };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
66
|
/**
|
|
66
67
|
* 构造 pi-ai 的模型描述符。
|
|
67
68
|
*
|
|
@@ -81,9 +82,8 @@ export function buildModel(options) {
|
|
|
81
82
|
const meta = options.meta;
|
|
82
83
|
const signedContextWindow = options.contextWindow && options.contextWindow > 0 ? options.contextWindow : undefined;
|
|
83
84
|
const signedMaxTokens = options.maxTokens && options.maxTokens > 0 ? options.maxTokens : undefined;
|
|
84
|
-
const contextWindow = signedContextWindow ?? meta?.contextWindow ??
|
|
85
|
-
const maxTokens = signedMaxTokens ?? meta?.maxTokens ??
|
|
86
|
-
const clamped = clampForModel(modelId, contextWindow, maxTokens);
|
|
85
|
+
const contextWindow = signedContextWindow ?? meta?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
86
|
+
const maxTokens = signedMaxTokens ?? meta?.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
87
87
|
const input = options.inputImages === false ? ['text'] : meta?.input?.length ? [...meta.input] : ['text', 'image'];
|
|
88
88
|
|
|
89
89
|
return {
|
|
@@ -96,8 +96,10 @@ export function buildModel(options) {
|
|
|
96
96
|
reasoning: options.reasoning !== false,
|
|
97
97
|
...(options.reasoning === false ? {} : { thinkingLevelMap: { ...THINKING_LEVEL_MAP } }),
|
|
98
98
|
cost: { ...NO_COST },
|
|
99
|
-
contextWindow
|
|
100
|
-
|
|
99
|
+
contextWindow,
|
|
100
|
+
// 只保证「输出不超过上下文」这一条一致性;不再按模型 id 打 262144/32768 的补丁
|
|
101
|
+
// (那是客户端主进程的保守特判,实测网关支持 1M / 131072,见文件头)。
|
|
102
|
+
maxTokens: Math.min(maxTokens, contextWindow),
|
|
101
103
|
compat: {
|
|
102
104
|
// SCNet 网关是 vLLM/OpenAI 传统拼写:`max_tokens` 而不是 `max_completion_tokens`。
|
|
103
105
|
maxTokensField: 'max_tokens',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-scnet-connect",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "把 SCNet 模型接入 DeepSeek Harness,可免客户端登录,使用免费模型(SCNet-Max,基座基本可确定为 DeepSeek Flash)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"@earendil-works/pi-ai": ">=1.0.0 <2.0.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
|
-
"check": "node tools/check.mjs"
|
|
49
|
+
"check": "node tools/check.mjs",
|
|
50
|
+
"check:fetch": "node tools/check-fetch.mjs && node tools/check-classify.mjs"
|
|
50
51
|
},
|
|
51
52
|
"repository": {
|
|
52
53
|
"type": "git",
|